一、套接字概念
Socket本身有“插座”的意思,在Linux環(huán)境下,用于表示進程間網絡通信的特殊文件類型。本質為內核借助緩沖區(qū)形成的偽文件。
既然是文件,那么理所當然的,我們可以使用文件描述符引用套接字。與管道類似的,Linux系統(tǒng)將其封裝成文件的目的是為了統(tǒng)一接口,使得讀寫套接字和讀寫文件的操作一致。區(qū)別是管道主要
應用于本地進程間通信,而套接字多應用于網絡進程間數(shù)據(jù)的傳遞。
套接字的內核實現(xiàn)較為復雜,不宜在學習初期深入學習。
在TCP/IP協(xié)議中,“IP地址+TCP或UDP端口號”唯一標識網絡通訊中的一個進程?!癐P地址+端口號”就對應一個socket。欲建立連接的兩個進程各自有一個socket來標識,那么這兩個socket組成的
socket pair就唯一標識一個連接。因此可以用Socket來描述網絡連接的一對一關系。
套接字通信原理如下圖所示:
在網絡通信中,套接字一定是成對出現(xiàn)的。一端的發(fā)送緩沖區(qū)對應對端的接收緩沖區(qū)。我們使用同一個文件描述符發(fā)送緩沖區(qū)和接收緩沖區(qū)。
TCP/IP協(xié)議最早在BSD UNIX上實現(xiàn),為TCP/IP協(xié)議設計的應用層編程接口稱為socket API。本章的主要內容是socket API,主要介紹TCP協(xié)議的函數(shù)接口,最后介紹UDP協(xié)議和UNIX Domain
Socket的函數(shù)接口。
二、預備知識
1.?網絡字節(jié)序
我們已經知道,內存中的多字節(jié)數(shù)據(jù)相對于內存地址有大端和小端之分,磁盤文件中的多字節(jié)數(shù)據(jù)相對于文件中的偏移地址也有大端小端之分。網絡數(shù)據(jù)流同樣有大端小端之分,那么如何定義網
絡數(shù)據(jù)流的地址呢?發(fā)送主機通常將發(fā)送緩沖區(qū)中的數(shù)據(jù)按內存地址從低到高的順序發(fā)出,接收主機把從網絡上接到的字節(jié)依次保存在接收緩沖區(qū)中,也是按內存地址從低到高的順序保存,因此,
網絡數(shù)據(jù)流的地址應這樣規(guī)定:先發(fā)出的數(shù)據(jù)是低地址,后發(fā)出的數(shù)據(jù)是高地址。
TCP/IP協(xié)議規(guī)定,網絡數(shù)據(jù)流應采用大端字節(jié)序,即低地址高字節(jié)。例如上一節(jié)的UDP段格式,地址0-1是16位的源端口號,如果這個端口號是1000(0x3e8),則地址0是0x03,地址1是0xe8,
也就是先發(fā)0x03,再發(fā)0xe8,這16位在發(fā)送主機的緩沖區(qū)中也應該是低地址存0x03,高地址存0xe8。但是,如果發(fā)送主機是小端字節(jié)序的,這16位被解釋成0xe803,而不是1000。因此,發(fā)送主機
把1000填到發(fā)送緩沖區(qū)之前需要做字節(jié)序的轉換。同樣地,接收主機如果是小端字節(jié)序的,接到16位的源端口號也要做字節(jié)序的轉換。如果主機是大端字節(jié)序的,發(fā)送和接收都不需要做轉換。同
理,32位的IP地址也要考慮網絡字節(jié)序和主機字節(jié)序的問題。
為使網絡程序具有可移植性,使同樣的C代碼在大端和小端計算機上編譯后都能正常運行,可以調用以下庫函數(shù)做網絡字節(jié)序和主機字節(jié)序的轉換。
#include uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);
h表示host,n表示network,l表示32位長整數(shù),s表示16位短整數(shù)。
如果主機是小端字節(jié)序,這些函數(shù)將參數(shù)做相應的大小端轉換然后返回,如果主機是大端字節(jié)序,這些函數(shù)不做轉換,將參數(shù)原封不動地返回。
主機字節(jié)順序? -->? 網絡字節(jié)順序:
uint16_t htons(uint16_t hostshort); 端口 uint32_t htonl(uint32_t hostlong); IP
網絡字節(jié)順序 --> 主機字節(jié)順序
uint16_t ntohs(uint16_t netshort); 端口 uint32_t ntohl(uint32_t netlong); IP
2.?IP地址轉換函數(shù)
早期:
#include #include #include int inet_aton(const char *cp, struct in_addr *inp); in_addr_t inet_addr(const char *cp); char *inet_ntoa(struct in_addr in); 只能處理IPv4的ip地址 注意參數(shù)是struct in_addr
現(xiàn)在:
#include int inet_pton(int af, const char *src, void *dst); const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
其中inet_pton和inet_ntop不僅可以轉換IPv4的in_addr,還可以轉換IPv6的in6_addr。
因此函數(shù)接口是void *addrptr。
3.?sockaddr數(shù)據(jù)結構
strcut sockaddr 很多網絡編程函數(shù)誕生早于IPv4協(xié)議,那時候都使用的是sockaddr結構體,為了向前兼容,現(xiàn)在sockaddr退化成了(void *)的作用,傳遞一個地址給函數(shù),至于這個函數(shù)是
sockaddr_in還是sockaddr_in6,由地址族確定,然后函數(shù)內部再強制類型轉化為所需的地址類型。
struct sockaddr { sa_family_t sa_family; /* address family, AF_xxx */ char sa_data[14]; /* 14 bytes of protocol address */ };
使用 sudo grep -r "struct sockaddr_in {" ?/usr 命令可查看到struct sockaddr_in結構體的定義。一般其默認的存儲位置:/usr/include/linux/in.h 文件中。
struct sockaddr_in { __kernel_sa_family_t sin_family; /* Address family */ 地址結構類型 __be16 sin_port; /* Port number */ 端口號 struct in_addr sin_addr; /* Internet address */ IP地址 /* Pad to size of `struct sockaddr'. */ unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)]; }; struct in_addr { /* Internet address. */ __be32 s_addr; }; struct sockaddr_in6 { unsigned short int sin6_family; /* AF_INET6 */ __be16 sin6_port; /* Transport layer port # */ __be32 sin6_flowinfo; /* IPv6 flow information */ struct in6_addr sin6_addr; /* IPv6 address */ __u32 sin6_scope_id; /* scope id (new in RFC2553) */ };
struct in6_addr { union { __u8 u6_addr8[16]; __be16 u6_addr16[8]; __be32 u6_addr32[4]; } in6_u; #define s6_addr in6_u.u6_addr8 #define s6_addr16 in6_u.u6_addr16 #define s6_addr32 in6_u.u6_addr32 }; #define UNIX_PATH_MAX 108 struct sockaddr_un { __kernel_sa_family_t sun_family; /* AF_UNIX */ char sun_path[UNIX_PATH_MAX]; /* pathname */ };
IPv4和IPv6的地址格式定義在netinet/in.h中,IPv4地址用sockaddr_in結構體表示,包括16位端口號和32位IP地址,IPv6地址用sockaddr_in6結構體表示,包括16位端口號、128位IP地址和一些控
制字段。UNIX Domain Socket的地址格式定義在sys/un.h中,用sock-addr_un結構體表示。各種socket地址結構體的開頭都是相同的,前16位表示整個結構體的長度(并不是所有UNIX的實現(xiàn)都有長
度字段,如Linux就沒有),后16位表示地址類型。IPv4、IPv6和Unix Domain Socket的地址類型分別定義為常數(shù)AF_INET、AF_INET6、AF_UNIX。這樣,只要取得某種sockaddr結構體的首地址,
不需要知道具體是哪種類型的sockaddr結構體,就可以根據(jù)地址類型字段確定結構體中的內容。因此,socket API可以接受各種類型的sockaddr結構體指針做參數(shù),例如bind、accept、connect等函
數(shù),這些函數(shù)的參數(shù)應該設計成void *類型以便接受各種類型的指針,但是sock API的實現(xiàn)早于ANSI C標準化,那時還沒有void *類型,因此這些函數(shù)的參數(shù)都用struct sockaddr *類型表示,在傳遞參
數(shù)之前要強制類型轉換一下,例如:
struct sockaddr_in servaddr; bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)); /* initialize servaddr */
三、套接字函數(shù)
1.?socket模型創(chuàng)建流程圖
socket API
2.?socket函數(shù)
#include /* See NOTES */ #include int socket(int domain, int type, int protocol); domain: AF_INET 這是大多數(shù)用來產生socket的協(xié)議,使用TCP或UDP來傳輸,用IPv4的地址 AF_INET6 與上面類似,不過是來用IPv6的地址 AF_UNIX 本地協(xié)議,使用在Unix和Linux系統(tǒng)上,一般都是當客戶端和服務器在同一臺及其上的時候使用 type: SOCK_STREAM 這個協(xié)議是按照順序的、可靠的、數(shù)據(jù)完整的基于字節(jié)流的連接。這是一個使用最多的socket類型,這個socket是使用TCP來進行傳輸。 SOCK_DGRAM 這個協(xié)議是無連接的、固定長度的傳輸調用。該協(xié)議是不可靠的,使用UDP來進行它的連接。 SOCK_SEQPACKET該協(xié)議是雙線路的、可靠的連接,發(fā)送固定長度的數(shù)據(jù)包進行傳輸。必須把這個包完整的接受才能進行讀取。 SOCK_RAW socket類型提供單一的網絡訪問,這個socket類型使用ICMP公共協(xié)議。(ping、traceroute使用該協(xié)議) SOCK_RDM 這個類型是很少使用的,在大部分的操作系統(tǒng)上沒有實現(xiàn),它是提供給數(shù)據(jù)鏈路層使用,不保證數(shù)據(jù)包的順序 protocol: 傳0 表示使用默認協(xié)議。 返回值: 成功:返回指向新創(chuàng)建的socket的文件描述符,失?。悍祷?1,設置errno
socket()打開一個網絡通訊端口,如果成功的話,就像open()一樣返回一個文件描述符,應用程序可以像讀寫文件一樣用read/write在網絡上收發(fā)數(shù)據(jù),如果socket()調用出錯則返回-1。對于IPv4,
domain參數(shù)指定為AF_INET。對于TCP協(xié)議,type參數(shù)指定為SOCK_STREAM,表示面向流的傳輸協(xié)議。如果是UDP協(xié)議,則type參數(shù)指定為SOCK_DGRAM,表示面向數(shù)據(jù)報的傳輸協(xié)議。
protocol參數(shù)的介紹從略,指定為0即可。
3.?bind函數(shù)
#include /* See NOTES */ #include int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); sockfd: socket文件描述符 addr: 構造出IP地址加端口號 addrlen: sizeof(addr)長度 返回值: 成功返回0,失敗返回-1, 設置errno
服務器程序所監(jiān)聽的網絡地址和端口號通常是固定不變的,客戶端程序得知服務器程序的地址和端口號后就可以向服務器發(fā)起連接,因此服務器需要調用bind綁定一個固定的網絡地址和端口號。
bind()的作用是將參數(shù)sockfd和addr綁定在一起,使sockfd這個用于網絡通訊的文件描述符監(jiān)聽addr所描述的地址和端口號。前面講過,struct sockaddr *是一個通用指針類型,addr參數(shù)實際上可
以接受多種協(xié)議的sockaddr結構體,而它們的長度各不相同,所以需要第三個參數(shù)addrlen指定結構體的長度。如:
struct sockaddr_in servaddr; bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(6666);
首先將整個結構體清零,然后設置地址類型為AF_INET,網絡地址為INADDR_ANY,這個宏表示本地的任意IP地址,因為服務器可能有多個網卡,每個網卡也可能綁定多個IP地址,這樣設置可以
在所有的IP地址上監(jiān)聽,直到與某個客戶端建立了連接時才確定下來到底用哪個IP地址,端口號為6666。
4. listen函數(shù)
#include /* See NOTES */ #include int listen(int sockfd, int backlog); sockfd: socket文件描述符 backlog: 排隊建立3次握手隊列和剛剛建立3次握手隊列的鏈接數(shù)和
查看系統(tǒng)默認backlog
cat /proc/sys/net/ipv4/tcp_max_syn_backlog
典型的服務器程序可以同時服務于多個客戶端,當有客戶端發(fā)起連接時,服務器調用的accept()返回并接受這個連接,如果有大量的客戶端發(fā)起連接而服務器來不及處理,尚未accept的客戶端就處
于連接等待狀態(tài),listen()聲明sockfd處于監(jiān)聽狀態(tài),并且最多允許有backlog個客戶端處于連接待狀態(tài),如果接收到更多的連接請求就忽略。listen()成功返回0,失敗返回-1。
5.?accept函數(shù)
#include /* See NOTES */ #include int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); sockdf: socket文件描述符 addr: 傳出參數(shù),返回鏈接客戶端地址信息,含IP地址和端口號 addrlen: 傳入傳出參數(shù)(值-結果),傳入sizeof(addr)大小,函數(shù)返回時返回真正接收到地址結構體的大小 返回值: 成功返回一個新的socket文件描述符,用于和客戶端通信,失敗返回-1,設置errno

三方握手完成后,服務器調用accept()接受連接,如果服務器調用accept()時還沒有客戶端的連接請求,就阻塞等待直到有客戶端連接上來。addr是一個傳出參數(shù),accept()返回時傳出客戶端的地
址和端口號。addrlen參數(shù)是一個傳入傳出參數(shù)(value-result argument),傳入的是調用者提供的緩沖區(qū)addr的長度以避免緩沖區(qū)溢出問題,傳出的是客戶端地址結構體的實際長度(有可能沒有占
滿調用者提供的緩沖區(qū))。如果給addr參數(shù)傳NULL,表示不關心客戶端的地址。
我們的服務器程序結構是這樣的:
while (1) { cliaddr_len = sizeof(cliaddr); connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); n = read(connfd, buf, MAXLINE); ...... close(connfd); }
整個是一個while死循環(huán),每次循環(huán)處理一個客戶端連接。由于cliaddr_len是傳入傳出參數(shù),每次調用accept()之前應該重新賦初值。accept()的參數(shù)listenfd是先前的監(jiān)聽文件描述符,而accept()的
返回值是另外一個文件描述符connfd,之后與客戶端之間就通過這個connfd通訊,最后關閉connfd斷開連接,而不關閉listenfd,再次回到循環(huán)開頭listenfd仍然用作accept的參數(shù)。accept()成功返回
一個文件描述符,出錯返回-1。
6.?connect函數(shù)
#include /* See NOTES */ #include int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); sockdf: socket文件描述符 addr: 傳入參數(shù),指定服務器端地址信息,含IP地址和端口號 addrlen: 傳入參數(shù),傳入sizeof(addr)大小 返回值: 成功返回0,失敗返回-1,設置errno
客戶端需要調用connect()連接服務器,connect和bind的參數(shù)形式一致,區(qū)別在于bind的參數(shù)是自己的地址,而connect的參數(shù)是對方的地址。connect()成功返回0,出錯返回-1。
7.? 數(shù)據(jù)接收
ssize_t read(int fd, void *buf, size_t count); ssize_t recv(int sockfd, void *buf, size_t len, int flags);
8. 數(shù)據(jù)發(fā)送
ssize_t write(int fd, const void *buf, size_t count); ssize_t send(int sockfd, const void *buf, size_t len, int flags);
flags 賦值為0
四、C/S模型-TCP
下圖是基于TCP協(xié)議的客戶端/服務器程序的一般流程:
服務器調用socket()、bind()、listen()完成初始化后,調用accept()阻塞等待,處于監(jiān)聽端口的狀態(tài),客戶端調用socket()初始化后,調用connect()發(fā)出SYN段并阻塞等待服務器應答,服務器應答一
個SYN-ACK段,客戶端收到后從connect()返回,同時應答一個ACK段,服務器收到后從accept()返回。
數(shù)據(jù)傳輸?shù)倪^程:
建立連接后,TCP協(xié)議提供全雙工的通信服務,但是一般的客戶端/服務器程序的流程是由客戶端主動發(fā)起請求,服務器被動處理請求,一問一答的方式。因此,服務器從accept()返回后立刻調用
read(),讀socket就像讀管道一樣,如果沒有數(shù)據(jù)到達就阻塞等待,這時客戶端調用write()發(fā)送請求給服務器,服務器收到后從read()返回,對客戶端的請求進行處理,在此期間客戶端調用read()阻塞
等待服務器的應答,服務器調用write()將處理結果發(fā)回給客戶端,再次調用read()阻塞等待下一條請求,客戶端收到后從read()返回,發(fā)送下一條請求,如此循環(huán)下去。
如果客戶端沒有更多的請求了,就調用close()關閉連接,就像寫端關閉的管道一樣,服務器的read()返回0,這樣服務器就知道客戶端關閉了連接,也調用close()關閉連接。注意,任何一方調用
close()后,連接的兩個傳輸方向都關閉,不能再發(fā)送數(shù)據(jù)了。如果一方調用shutdown()則連接處于半關閉狀態(tài),仍可接收對方發(fā)來的數(shù)據(jù)。
在學習socket API時要注意應用程序和TCP協(xié)議層是如何交互的: 應用程序調用某個socket函數(shù)時TCP協(xié)議層完成什么動作,比如調用connect()會發(fā)出SYN段 應用程序如何知道TCP協(xié)議層的狀態(tài)變
化,比如從某個阻塞的socket函數(shù)返回就表明TCP協(xié)議收到了某些段,再比如read()返回0就表明收到了FIN段。
1. server
下面通過最簡單的客戶端/服務器程序的實例來學習socket API。
server.c的作用是從客戶端讀字符,然后將每個字符轉換為大寫并回送給客戶端。
server.c
1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 11 12 int main(int argc, const char* argv[]) 13 { 14 15 int lfd = socket(AF_INET, SOCK_STREAM, 0); 16 if(lfd == -1) 17 { 18 perror("socket error"); 19 exit(1); 20 } 21 22 23 struct sockaddr_in serv_addr; 24 25 memset(&serv_addr, 0, sizeof(serv_addr)); 26 27 serv_addr.sin_family = AF_INET; 28 serv_addr.sin_port = htons(6666); 29 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 30 31 int ret = bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 32 if(ret == -1) 33 { 34 perror("bind error"); 35 exit(1); 36 } 37 38 39 ret = listen(lfd, 64); 40 if(ret == -1) 41 { 42 perror("listen error"); 43 exit(1); 44 } 45 46 47 struct sockaddr_in cline_addr; 48 socklen_t clien_len = sizeof(cline_addr); 49 int cfd = accept(lfd, (struct sockaddr*)&cline_addr, &clien_len); 50 if(cfd == -1) 51 { 52 perror("accept error"); 53 exit(1); 54 } 55 56 char ipbuf[64]; 57 58 printf("cliient ip: %s, port: %d\n", 59 inet_ntop(AF_INET, &cline_addr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)), 60 ntohs(cline_addr.sin_port)); 61 62 63 while(1) 64 { 65 66 char buf[1024] = {0}; 67 int len = read(cfd, buf, sizeof(buf)); 68 if(len == -1) 69 { 70 perror("read error"); 71 break; 72 } 73 else if(len > 0) 74 { 75 76 printf("read buf = %s\n", buf); 77 // 小寫 -》 大寫 78 for(int i=0; i2.?client
client.c的作用是從命令行參數(shù)中獲得一個字符串發(fā)給服務器,然后接收服務器返回的字符串并打印。
client.c
1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 10 // tcp client 11 int main(int argc, const char* argv[]) 12 { 13 // 創(chuàng)建套接字 14 int fd = socket(AF_INET, SOCK_STREAM, 0); 15 if(fd == -1) 16 { 17 perror("socket error"); 18 exit(1); 19 } 20 21 // 連接服務器 22 struct sockaddr_in serv_addr; 23 memset(&serv_addr, 0, sizeof(serv_addr)); 24 serv_addr.sin_family = AF_INET; 25 serv_addr.sin_port = htons(9999); 26 inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr); 27 int ret = connect(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 28 if(ret == -1) 29 { 30 perror("connect error"); 31 exit(1); 32 } 33 34 // 通信 35 while(1) 36 { 37 // 寫數(shù)據(jù) 38 // 接收鍵盤輸入 39 char buf[512]; 40 fgets(buf, sizeof(buf), stdin); 41 // 發(fā)送給服務器 42 write(fd, buf, strlen(buf)+1); 43 44 // 接收服務器端的數(shù)據(jù) 45 int len = read(fd, buf, sizeof(buf)); 46 if (len == -1) 47 { 48 perror("read error"); 49 exit(1); 50 } 51 else if (len == 0) 52 { 53 printf("server closed\n"); 54 break; 55 } 56 else 57 { 58 printf("read buf = %s, len = %d\n", buf, len); 59 } 60 } 61 62 close(fd); 63 64 return 0; 65 }
由于客戶端不需要固定的端口號,因此不必調用bind(),客戶端的端口號由內核自動分配。注意,客戶端不是不允許調用bind(),只是沒有必要調用bind()固定一個端口號,服務器也不是必須調用
bind(),但如果服務器不調用bind(),內核會自動給服務器分配監(jiān)聽端口,每次啟動服務器時端口號都不一樣,客戶端要連接服務器就會遇到麻煩。
客戶端和服務器啟動后可以使用netstat命令查看鏈接情況:
netstat -apn|grep 6666
五、出錯處理封裝函數(shù)
上面的例子不僅功能簡單,而且簡單到幾乎沒有什么錯誤處理,我們知道,系統(tǒng)調用不能保證每次都成功,必須進行出錯處理,這樣一方面可以保證程序邏輯正常,另一方面可以迅速得到故障信
息。
為使錯誤處理的代碼不影響主程序的可讀性,我們把與socket相關的一些系統(tǒng)函數(shù)加上錯誤處理代碼包裝成新的函數(shù),做成一個模塊wrap.c及wrap.h:
wrap.c
1 #include 2 #include 3 #include 4 #include 5 #include 6 7 void perr_exit(const char *s) 8 { 9 perror(s); 10 exit(-1); 11 } 12 13 int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr) 14 { 15 int n; 16 17 again: 18 if ((n = accept(fd, sa, salenptr)) < 0) 19 { 20 //ECONNABORTED 發(fā)生在重傳(一定次數(shù))失敗后,強制關閉套接字 21 //EINTR 進程被信號中斷 22 if ((errno == ECONNABORTED) || (errno == EINTR)) 23 { 24 goto again; 25 } 26 else 27 { 28 perr_exit("accept error"); 29 } 30 } 31 return n; 32 } 33 34 int Bind(int fd, const struct sockaddr *sa, socklen_t salen) 35 { 36 int n; 37 38 if ((n = bind(fd, sa, salen)) < 0) 39 { 40 perr_exit("bind error"); 41 } 42 43 return n; 44 } 45 46 int Connect(int fd, const struct sockaddr *sa, socklen_t salen) 47 { 48 int n; 49 n = connect(fd, sa, salen); 50 if (n < 0) 51 { 52 perr_exit("connect error"); 53 } 54 55 return n; 56 } 57 58 int Listen(int fd, int backlog) 59 { 60 int n; 61 62 if ((n = listen(fd, backlog)) < 0) 63 { 64 perr_exit("listen error"); 65 } 66 67 return n; 68 } 69 70 int Socket(int family, int type, int protocol) 71 { 72 int n; 73 74 if ((n = socket(family, type, protocol)) < 0) 75 { 76 perr_exit("socket error"); 77 } 78 79 return n; 80 } 81 82 ssize_t Read(int fd, void *ptr, size_t nbytes) 83 { 84 ssize_t n; 85 86 again: 87 if ( (n = read(fd, ptr, nbytes)) == -1) 88 { 89 if (errno == EINTR) 90 goto again; 91 else 92 return -1; 93 } 94 95 return n; 96 } 97 98 ssize_t Write(int fd, const void *ptr, size_t nbytes) 99 { 100 ssize_t n; 101 102 again: 103 if ((n = write(fd, ptr, nbytes)) == -1) 104 { 105 if (errno == EINTR) 106 goto again; 107 else 108 return -1; 109 } 110 return n; 111 } 112 113 int Close(int fd) 114 { 115 int n; 116 if ((n = close(fd)) == -1) 117 perr_exit("close error"); 118 119 return n; 120 } 121 122 /*參三: 應該讀取的字節(jié)數(shù)*/ 123 //socket 4096 readn(cfd, buf, 4096) nleft = 4096-1500 124 ssize_t Readn(int fd, void *vptr, size_t n) 125 { 126 size_t nleft; //usigned int 剩余未讀取的字節(jié)數(shù) 127 ssize_t nread; //int 實際讀到的字節(jié)數(shù) 128 char *ptr; 129 130 ptr = vptr; 131 nleft = n; //n 未讀取字節(jié)數(shù) 132 133 while (nleft > 0) 134 { 135 if ((nread = read(fd, ptr, nleft)) < 0) 136 { 137 if (errno == EINTR) 138 { 139 nread = 0; 140 } 141 else 142 { 143 return -1; 144 } 145 } 146 else if (nread == 0) 147 { 148 break; 149 } 150 151 nleft -= nread; //nleft = nleft - nread 152 ptr += nread; 153 } 154 return n - nleft; 155 } 156 157 ssize_t Writen(int fd, const void *vptr, size_t n) 158 { 159 size_t nleft; 160 ssize_t nwritten; 161 const char *ptr; 162 163 ptr = vptr; 164 nleft = n; 165 while (nleft > 0) 166 { 167 if ( (nwritten = write(fd, ptr, nleft)) <= 0) 168 { 169 if (nwritten < 0 && errno == EINTR) 170 nwritten = 0; 171 else 172 return -1; 173 } 174 nleft -= nwritten; 175 ptr += nwritten; 176 } 177 return n; 178 } 179 180 static ssize_t my_read(int fd, char *ptr) 181 { 182 static int read_cnt; 183 static char *read_ptr; 184 static char read_buf[100]; 185 186 if (read_cnt <= 0) { 187 again: 188 if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) //"hello\n" 189 { 190 if (errno == EINTR) 191 goto again; 192 return -1; 193 } 194 else if (read_cnt == 0) 195 return 0; 196 197 read_ptr = read_buf; 198 } 199 read_cnt--; 200 *ptr = *read_ptr++; 201 202 return 1; 203 } 204 205 /*readline --- fgets*/ 206 //傳出參數(shù) vptr 207 ssize_t Readline(int fd, void *vptr, size_t maxlen) 208 { 209 ssize_t n, rc; 210 char c, *ptr; 211 ptr = vptr; 212 213 for (n = 1; n < maxlen; n++) 214 { 215 if ((rc = my_read(fd, &c)) == 1) //ptr[] = hello\n 216 { 217 *ptr++ = c; 218 if (c == '\n') 219 break; 220 } 221 else if (rc == 0) 222 { 223 *ptr = 0; 224 return n-1; 225 } 226 else 227 return -1; 228 } 229 *ptr = 0; 230 231 return n; 232 }
wrap.h
1 #ifndef __WRAP_H_ 2 #define __WRAP_H_ 3 4 void perr_exit(const char *s); 5 int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr); 6 int Bind(int fd, const struct sockaddr *sa, socklen_t salen); 7 int Connect(int fd, const struct sockaddr *sa, socklen_t salen); 8 int Listen(int fd, int backlog); 9 int Socket(int family, int type, int protocol); 10 ssize_t Read(int fd, void *ptr, size_t nbytes); 11 ssize_t Write(int fd, const void *ptr, size_t nbytes); 12 int Close(int fd); 13 ssize_t Readn(int fd, void *vptr, size_t n); 14 ssize_t Writen(int fd, const void *vptr, size_t n); 15 ssize_t my_read(int fd, char *ptr); 16 ssize_t Readline(int fd, void *vptr, size_t maxlen); 17 18 #endif
使用上面的封裝實現(xiàn)一個server和client:
server.c
1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 10 #include "wrap.h" 11 12 #define SERV_PORT 6666 13 14 int main(void) 15 { 16 int sfd, cfd; 17 int len, i; 18 char buf[BUFSIZ], clie_IP[128]; 19 20 struct sockaddr_in serv_addr, clie_addr; 21 socklen_t clie_addr_len; 22 23 sfd = Socket(AF_INET, SOCK_STREAM, 0); 24 25 bzero(&serv_addr, sizeof(serv_addr)); 26 serv_addr.sin_family = AF_INET; 27 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 28 serv_addr.sin_port = htons(SERV_PORT); 29 30 Bind(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); 31 32 Listen(sfd, 2); 33 34 printf("wait for client connect ...\n"); 35 36 clie_addr_len = sizeof(clie_addr_len); 37 cfd = Accept(sfd, (struct sockaddr *)&clie_addr, &clie_addr_len); 38 printf("cfd = ----%d\n", cfd); 39 40 printf("client IP: %s port:%d\n", 41 inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)), 42 ntohs(clie_addr.sin_port)); 43 44 while (1) 45 { 46 len = Read(cfd, buf, sizeof(buf)); 47 Write(STDOUT_FILENO, buf, len); 48 49 for (i = 0; i < len; i++) 50 buf[i] = toupper(buf[i]); 51 Write(cfd, buf, len); 52 } 53 54 Close(sfd); 55 Close(cfd); 56 57 return 0; 58 }
client.c
1 #include 2 #include 3 #include 4 #include 5 #include 6 7 #include "wrap.h" 8 9 #define SERV_IP "127.0.0.1" 10 #define SERV_PORT 6666 11 12 int main(void) 13 { 14 int sfd, len; 15 struct sockaddr_in serv_addr; 16 char buf[BUFSIZ]; 17 18 sfd = Socket(AF_INET, SOCK_STREAM, 0); 19 20 bzero(&serv_addr, sizeof(serv_addr)); 21 serv_addr.sin_family = AF_INET; 22 inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr); 23 serv_addr.sin_port = htons(SERV_PORT); 24 25 Connect(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); 26 27 while (1) { 28 fgets(buf, sizeof(buf), stdin); 29 int r = Write(sfd, buf, strlen(buf)); 30 printf("Write r ======== %d\n", r); 31 len = Read(sfd, buf, sizeof(buf)); 32 printf("Read len ========= %d\n", len); 33 Write(STDOUT_FILENO, buf, len); 34 } 35 36 Close(sfd); 37 38 return 0; 39 }
makefile
1 src = $(wildcard *.c) 2 obj = $(patsubst %.c, %.o, $(src)) 3 4 all: server client 5 6 server: server.o wrap.o 7 gcc server.o wrap.o -o server -Wall 8 client: client.o wrap.o 9 gcc client.o wrap.o -o client -Wall 10 11 %.o:%.c 12 gcc -c $< -Wall 13 14 .PHONY: clean all 15 clean: 16 -rm -rf server client $(obj)
Linux Socket編程
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。