linux網絡編程socket套接字

套接字概念

Socket本身有“插座”的意思,在Linux環境下,用于表示進程間網絡通信的特殊文件類型。本質為內核借助緩沖區形成的偽文件。

既然是文件,那么理所當然的,我們可以使用文件描述符引用套接字。與管道類似的,Linux系統將其封裝成文件的目的是為了統一接口,使得讀寫套接字和讀寫文件的操作一致。區別是管道主要應用于本地進程間通信,而套接字多應用于網絡進程間數據的傳遞。

在TCP/IP協議中,“IP地址+TCP或UDP端口號”唯一標識網絡通訊中的一個進程。“IP地址+端口號”就對應一個socket。欲建立連接的兩個進程各自有一個socket來標識,那么這兩個socket組成的socket pair就唯一標識一個連接。因此可以用Socket來描述網絡連接的一對一關系。

套接字通信原理如下圖所示:

在網絡通信中,套接字一定是成對出現的。一端的發送緩沖區對應對端的接收緩沖區。我們使用同一個文件描述符索發送緩沖區和接收緩沖區。

TCP/IP協議最早在BSD UNIX上實現,為TCP/IP協議設計的應用層編程接口稱為socket API。本文的主要內容是socket API,主要介紹TCP協議的函數接口。

預備知識

網絡字節序

我們已經知道,內存中的多字節數據相對于內存地址有大端和小端之分,磁盤文件中的多字節數據相對于文件中的偏移地址也有大端小端之分。網絡數據流同樣有大端小端之分,那么如何定義網絡數據流的地址呢?發送主機通常將發送緩沖區中的數據按內存地址從低到高的順序發出,接收主機把從網絡上接到的字節依次保存在接收緩沖區中,也是按內存地址從低到高的順序保存,因此,網絡數據流的地址應這樣規定:先發出的數據是低地址,后發出的數據是高地址。

TCP/IP協議規定,網絡數據流應采用大端字節序,即低地址高字節。同樣地,接收主機如果是小端字節序的,接到16位的源端口號也要做字節序的轉換。如果主機是大端字節序的,發送和接收都不需要做轉換。同理,32位的IP地址也要考慮網絡字節序和主機字節序的問題。

為使網絡程序具有可移植性,使同樣的C代碼在大端和小端計算機上編譯后都能正常運行,可以調用以下庫函數做網絡字節序和主機字節序的轉換

#include <arpa/inet.h>

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位長整數,s表示16位短整數。

htonl.c:

#include <arpa/inet.h>
#include <stdio.h>
int main(int argc, char *argv[])
{char buf[4] = {192, 168, 1, 2};int num = *(int *)buf; // 將buf首地址強轉為int*類型,// 然后再解引用得到里面的數據int sum = htonl(num); // 將主機字節序轉換為網絡字節序,// l表示32位長整數,s表示16位短整數unsigned char *p = &sum; // 將轉換得到的數據存入到p里printf("%d %d %d %d\n", *p, *(p + 1), *(p + 2), *(p + 3));unsigned short a = 0x0102;unsigned short b = htons(a);printf("%x\n", b);return 0;
}

ntohl.c:

#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{unsigned char buf[4]={1,1,168,192};int num = *(int *)buf;int sum = ntohl(num);//將網絡字節序轉換為主機字節序unsigned char *p = (unsigned char *)&sum;printf("%d %d %d %d\n",*p,*(p+1),*(p+2),*(p+3));return 0;
}

如果主機是小端字節序,這些函數將參數做相應的大小端轉換然后返回,如果主機是大端字節序,這些函數不做轉換,將參數原封不動地返回。

IP地址轉換函數

早期:

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

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地址

注意參數是struct in_addr

現在:

#include <arpa/inet.h>

int inet_pton(int af, const char *src, void *dst);

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

#include <arpa/inet.h>

?int inet_pton(int af, const char *src, void *dst);

功能:?將點分十進制串(即192.168.1.2)?轉成32位網絡大端的數據("192.168.1.2"? ==>? ?)

參數:

?? ?af :?

?? ?? ? AF_INET? ? ? ? ?IPV4

?? ?? ? AF_INET6? ? ? ? IPV6

? src:?點分十進制串的首地址?

??dst : 32位網絡數據的地址?

成功返回1

#include <arpa/inet.h>

const char *inet_ntop(int af, const void *src,

?????????????????????????????char *dst, socklen_t size);

功能:?將32位大端的網絡數據轉成點分十進制串

參數:

?? ?af : AF_INET

?? ?src :?32位大端的網絡數?地址

?? ?dst :?存儲點分十進制串?地址

?? ?size :?存儲點分制串數組的大小??

返回值:?存儲點分制串數組首地址

代碼示例:

#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{char buf[]="1.1.1.1";unsigned int num = 0;inet_pton(AF_INET,buf,&num);//將點分十進制串(即192.168.1.2)轉成32位網絡大端的數據unsigned char * p = (unsigned char *)&num;printf("%d %d %d %d\n",*p,*(p+1),*(p+2),*(p+3));printf("%d\n",num);//用完inet_pton函數后,num里存放的就是轉換的點分十進制的十進制數char ip[16]="";printf("%s\n",inet_ntop(AF_INET,&num,ip,16));//將32位大端的網絡數據轉成點分十進制串return 0;
}

支持IPv4和IPv6

其中inet_pton和inet_ntop不僅可以轉換IPv4的in_addr,還可以轉換IPv6的in6_addr。

因此函數接口是void *addrptr。

sockaddr數據結構

strcut sockaddr 很多網絡編程函數誕生早于IPv4協議,那時候都使用的是sockaddr結構體,為了向前兼容,現在sockaddr退化成了(void *)的作用,傳遞一個地址給函數,至于這個函數是sockaddr_in還是sockaddr_in6,由地址族確定,然后函數內部再強制類型轉化為所需的地址類型。

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 */

};

Pv4和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的實現都有長度字段,如Linux就沒有),后16位表示地址類型。IPv4、IPv6和Unix Domain Socket的地址類型分別定義為常數AF_INET、AF_INET6、AF_UNIX。這樣,只要取得某種sockaddr結構體的首地址,不需要知道具體是哪種類型的sockaddr結構體,就可以根據地址類型字段確定結構體中的內容。因此,socket API可以接受各種類型的sockaddr結構體指針做參數,例如bind、accept、connect等函數,這些函數的參數應該設計成void *類型以便接受各種類型的指針,但是sock API的實現早于ANSI C標準化,那時還沒有void *類型,因此這些函數的參數都用struct sockaddr *類型表示,在傳遞參數之前要強制類型轉換一下,例如:

struct sockaddr_in servaddr;

bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)); /* initialize servaddr */

網絡套接字函數

socket模型創建流程圖

socket函數

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

int socket(int domain, int type, int protocol);

domain:

AF_INET 這是大多數用來產生socket的協議,使用TCP或UDP來傳輸,用IPv4的地址

AF_INET6 與上面類似,不過是來用IPv6的地址

AF_UNIX 本地協議,使用在Unix和Linux系統上,一般都是當客戶端和服務器在同一臺及其上的時候使用

type:

SOCK_STREAM 這個協議是按照順序的、可靠的、數據完整的基于字節流的連接。這是一個使用最多的socket類型,這個socket是使用TCP來進行傳輸。

SOCK_DGRAM 這個協議是無連接的、固定長度的傳輸調用。該協議是不可靠的,使用UDP來進行它的連接。

SOCK_SEQPACKET該協議是雙線路的、可靠的連接,發送固定長度的數據包進行傳輸。必須把這個包完整的接受才能進行讀取。

SOCK_RAW socket類型提供單一的網絡訪問,這個socket類型使用ICMP公共協議。(ping、traceroute使用該協議)

SOCK_RDM 這個類型是很少使用的,在大部分的操作系統上沒有實現,它是提供給數據鏈路層使用,不保證數據包的順序

protocol:

傳0 表示使用默認協議。

返回值:

成功:返回指向新創建的socket的文件描述符

失敗:返回-1,設置errno

socket()打開一個網絡通訊端口,如果成功的話,就像open()一樣返回一個文件描述符,應用程序可以像讀寫文件一樣用read/write在網絡上收發數據,如果socket()調用出錯則返回-1。對于IPv4,domain參數指定為AF_INET。對于TCP協議,type參數指定為SOCK_STREAM,表示面向流的傳輸協議。如果是UDP協議,則type參數指定為SOCK_DGRAM,表示面向數據報的傳輸協議。protocol參數的介紹從略,指定為0即可。

bind函數

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:

socket文件描述符

addr:

構造出IP地址加端口號

addrlen:

sizeof(addr)長度

返回值:

成功返回0,失敗返回-1, 設置errno

服務器程序所監聽的網絡地址和端口號通常是固定不變的,客戶端程序得知服務器程序的地址和端口號后就可以向服務器發起連接,因此服務器需要調用bind綁定一個固定的網絡地址和端口號。

bind()的作用是將參數sockfd和addr綁定在一起,使sockfd這個用于網絡通訊的文件描述符監聽addr所描述的地址和端口號。前面講過,struct sockaddr *是一個通用指針類型,addr參數實際上可以接受多種協議的sockaddr結構體,而它們的長度各不相同,所以需要第三個參數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地址上監聽,直到與某個客戶端建立了連接時才確定下來到底用哪個IP地址,端口號為6666。

listen函數

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

int listen(int sockfd, int backlog);

sockfd:

socket文件描述符

backlog:

排隊建立3次握手隊列和剛剛建立3次握手隊列的鏈接數和

查看系統默認backlog

cat /proc/sys/net/ipv4/tcp_max_syn_backlog

典型的服務器程序可以同時服務于多個客戶端,當有客戶端發起連接時,服務器調用的accept()返回并接受這個連接,如果有大量的客戶端發起連接而服務器來不及處理,尚未accept的客戶端就處于連接等待狀態,listen()聲明sockfd處于監聽狀態,并且最多允許有backlog個客戶端處于連接待狀態,如果接收到更多的連接請求就忽略。listen()成功返回0,失敗返回-1。

accept函數

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockdf:

socket文件描述符

addr:

傳出參數,返回鏈接客戶端地址信息,含IP地址和端口號

addrlen:

傳入傳出參數(值-結果),傳入sizeof(addr)大小,函數返回時返回真正接收到地址結構體的大小

返回值:

成功返回一個新的socket文件描述符,用于和客戶端通信,失敗返回-1,設置errno

三方握手完成后,服務器調用accept()接受連接,如果服務器調用accept()時還沒有客戶端的連接請求,就阻塞等待直到有客戶端連接上來。addr是一個傳出參數,accept()返回時傳出客戶端的地址和端口號。addrlen參數是一個傳入傳出參數(value-result argument),傳入的是調用者提供的緩沖區addr的長度以避免緩沖區溢出問題,傳出的是客戶端地址結構體的實際長度(有可能沒有占滿調用者提供的緩沖區)。如果給addr參數傳NULL,表示不關心客戶端的地址。

我們的服務器程序結構是這樣的:

while (1) {

cliaddr_len = sizeof(cliaddr);

connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);

n = read(connfd, buf, MAXLINE);

......

close(connfd);

}

整個是一個while死循環,每次循環處理一個客戶端連接。由于cliaddr_len是傳入傳出參數,每次調用accept()之前應該重新賦初值。accept()的參數listenfd是先前的監聽文件描述符,而accept()的返回值是另外一個文件描述符connfd,之后與客戶端之間就通過這個connfd通訊,最后關閉connfd斷開連接,而不關閉listenfd,再次回到循環開頭listenfd仍然用作accept的參數。accept()成功返回一個文件描述符,出錯返回-1。

connect函數

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockdf:

socket文件描述符

addr:

傳入參數,指定服務器端地址信息,含IP地址和端口號

addrlen:

傳入參數,傳入sizeof(addr)大小

返回值:

成功返回0,失敗返回-1,設置errno

客戶端需要調用connect()連接服務器,connect和bind的參數形式一致,區別在于bind的參數是自己的地址,而connect的參數是對方的地址。connect()成功返回0,出錯返回-1。

C/S模型-TCP

下圖是基于TCP協議的客戶端/服務器程序的一般流程:

TCP協議通訊流程

服務器調用socket()、bind()、listen()完成初始化后,調用accept()阻塞等待,處于監聽端口的狀態,客戶端調用socket()初始化后,調用connect()發出SYN段并阻塞等待服務器應答,服務器應答一個SYN-ACK段,客戶端收到后從connect()返回,同時應答一個ACK段,服務器收到后從accept()返回。

數據傳輸的過程:

建立連接后,TCP協議提供全雙工的通信服務,但是一般的客戶端/服務器程序的流程是由客戶端主動發起請求,服務器被動處理請求,一問一答的方式。因此,服務器從accept()返回后立刻調用read(),讀socket就像讀管道一樣,如果沒有數據到達就阻塞等待,這時客戶端調用write()發送請求給服務器,服務器收到后從read()返回,對客戶端的請求進行處理,在此期間客戶端調用read()阻塞等待服務器的應答,服務器調用write()將處理結果發回給客戶端,再次調用read()阻塞等待下一條請求,客戶端收到后從read()返回,發送下一條請求,如此循環下去。

如果客戶端沒有更多的請求了,就調用close()關閉連接,就像寫端關閉的管道一樣,服務器的read()返回0,這樣服務器就知道客戶端關閉了連接,也調用close()關閉連接。注意,任何一方調用close()后,連接的兩個傳輸方向都關閉,不能再發送數據了。如果一方調用shutdown()則連接處于半關閉狀態,仍可接收對方發來的數據。

在學習socket API時要注意應用程序和TCP協議層是如何交互的: 應用程序調用某個socket函數時TCP協議層完成什么動作,比如調用connect()會發出SYN段 應用程序如何知道TCP協議層的狀態變化,比如從某個阻塞的socket函數返回就表明TCP協議收到了某些段,再比如read()返回0就表明收到了FIN段

server

下面通過最簡單的客戶端/服務器程序的實例來學習socket API。

server.c的作用是從客戶端讀字符,然后將每個字符轉換為大寫并回送給客戶端。

#include <stdio.h>  
#include <ctype.h>  
#include <sys/socket.h>  
#include <arpa/inet.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <errno.h>  
#include <pthread.h>  #define SERV_PORT 9527  void sys_err(const char *str)  
{  perror(str);  exit(1);  
}  int main(int argc, char *argv[])  
{  int lfd = 0, cfd = 0;  int ret, i;  char buf[BUFSIZ], client_IP[1024];  struct sockaddr_in serv_addr, clit_addr;  // 定義服務器地址結構 和 客戶端地址結構  socklen_t clit_addr_len;                  // 客戶端地址結構大小  serv_addr.sin_family = AF_INET;             // IPv4  serv_addr.sin_port = htons(SERV_PORT);      // 轉為網絡字節序的 端口號  serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // 獲取本機任意有效IP  lfd = socket(AF_INET, SOCK_STREAM, 0);      //創建一個 socket  if (lfd == -1) {  sys_err("socket error");  }  //給服務器socket綁定地址結構(IP+port)  bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));listen(lfd, 128);                   //  設置監聽上限  clit_addr_len = sizeof(clit_addr);  //  獲取客戶端地址結構大小  // 阻塞等待客戶 端連接請求 cfd = accept(lfd, (struct sockaddr *)&clit_addr, &clit_addr_len);if (cfd == -1)  sys_err("accept error");  // 根據accept傳出參數,獲取客戶端 ip 和 port printf("client ip:%s port:%d\n",inet_ntop(AF_INET, &clit_addr.sin_addr.s_addr, client_IP, sizeof(client_IP)),ntohs(clit_addr.sin_port));      while (1) {  ret = read(cfd, buf, sizeof(buf));      // 讀客戶端數據  write(STDOUT_FILENO, buf, ret);         // 寫到屏幕查看  for (i = 0; i < ret; i++)                // 小寫 -- 大寫  buf[i] = toupper(buf[i]);  write(cfd, buf, ret);                   // 將大寫,寫回給客戶端。  }  close(lfd);  close(cfd);  return 0;  
}  

client

client.c的作用是從命令行參數中獲得一個字符串發給服務器,然后接收服務器返回的字符串并打印。

#include <unistd.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>int main(int argc, char *argv[])
{//創建套接字int sock_fd;sock_fd = socket(AF_INET,SOCK_STREAM,0);//連接服務器struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(9527);inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr.s_addr);connect(sock_fd,(struct sockaddr *)&addr,sizeof(addr));//讀寫數據char buf[1024]="";while(1){int n = read(STDIN_FILENO,buf,sizeof(buf));//從終端讀數據,從鍵盤輸入readwrite(sock_fd,buf,n);//發送數據給服務器n = read(sock_fd,buf,sizeof(buf));write(STDOUT_FILENO,buf,n);printf("\n");//STDIN_FILENO:接收鍵盤的輸入////STDOUT_FILENO:向屏幕輸出}//關閉close(sock_fd);return 0;
}

結果演示:

由于客戶端不需要固定的端口號,因此不必調用bind(),客戶端的端口號由內核自動分配。注意,客戶端不是不允許調用bind(),只是沒有必要調用bind()固定一個端口號,服務器也不是必須調用bind(),但如果服務器不調用bind(),內核會自動給服務器分配監聽端口,每次啟動服務器時端口號都不一樣,客戶端要連接服務器就會遇到麻煩。

客戶端和服務器啟動后可以使用netstat命令查看鏈接情況:

netstat -apn|grep 6666

出錯處理封裝函數

上面的例子不僅功能簡單,而且簡單到幾乎沒有什么錯誤處理,我們知道,系統調用不能保證每次都成功,必須進行出錯處理,這樣一方面可以保證程序邏輯正常,另一方面可以迅速得到故障信息。

為使錯誤處理的代碼不影響主程序的可讀性,我們把與socket相關的一些系統函數加上錯誤處理代碼包裝成新的函數,做成一個模塊wrap.c:

wrap.c

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>void perr_exit(const char *s)
{perror(s);exit(-1);
}int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{int n;again:if ((n = accept(fd, sa, salenptr)) < 0) {if ((errno == ECONNABORTED) || (errno == EINTR))//如果是被信號中斷和軟件層次中斷,不能退出goto again;elseperr_exit("accept error");}return n;
}int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{int n;if ((n = bind(fd, sa, salen)) < 0)perr_exit("bind error");return n;
}int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{int n;if ((n = connect(fd, sa, salen)) < 0)perr_exit("connect error");return n;
}int Listen(int fd, int backlog)
{int n;if ((n = listen(fd, backlog)) < 0)perr_exit("listen error");return n;
}int Socket(int family, int type, int protocol)
{int n;if ((n = socket(family, type, protocol)) < 0)perr_exit("socket error");return n;
}ssize_t Read(int fd, void *ptr, size_t nbytes)
{ssize_t n;again:if ( (n = read(fd, ptr, nbytes)) == -1) {if (errno == EINTR)//如果是被信號中斷,不應該退出goto again;elsereturn -1;}return n;
}ssize_t Write(int fd, const void *ptr, size_t nbytes)
{ssize_t n;again:if ( (n = write(fd, ptr, nbytes)) == -1) {if (errno == EINTR)goto again;elsereturn -1;}return n;
}int Close(int fd)
{int n;if ((n = close(fd)) == -1)perr_exit("close error");return n;
}/*參三: 應該讀取固定的字節數數據*/
ssize_t Readn(int fd, void *vptr, size_t n)
{size_t  nleft;              //usigned int 剩余未讀取的字節數ssize_t nread;              //int 實際讀到的字節數char   *ptr;ptr = vptr;nleft = n;while (nleft > 0) {if ((nread = read(fd, ptr, nleft)) < 0) {if (errno == EINTR)nread = 0;elsereturn -1;} else if (nread == 0)break;nleft -= nread;ptr += nread;}return n - nleft;
}
/*:固定的字節數數據*/
ssize_t Writen(int fd, const void *vptr, size_t n)
{size_t nleft;ssize_t nwritten;const char *ptr;ptr = vptr;nleft = n;while (nleft > 0) {if ( (nwritten = write(fd, ptr, nleft)) <= 0) {if (nwritten < 0 && errno == EINTR)nwritten = 0;elsereturn -1;}nleft -= nwritten;ptr += nwritten;}return n;
}static ssize_t my_read(int fd, char *ptr)
{static int read_cnt;static char *read_ptr;static char read_buf[100];if (read_cnt <= 0) {
again:if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {if (errno == EINTR)goto again;return -1;} else if (read_cnt == 0)return 0;read_ptr = read_buf;}read_cnt--;*ptr = *read_ptr++;return 1;
}ssize_t Readline(int fd, void *vptr, size_t maxlen)
{ssize_t n, rc;char    c, *ptr;ptr = vptr;for (n = 1; n < maxlen; n++) {if ( (rc = my_read(fd, &c)) == 1) {*ptr++ = c;if (c  == '\n')break;} else if (rc == 0) {*ptr = 0;return n - 1;} elsereturn -1;}*ptr  = 0;return n;
}int tcp4bind(short port,const char *IP)
{struct sockaddr_in serv_addr;int lfd = Socket(AF_INET,SOCK_STREAM,0);bzero(&serv_addr,sizeof(serv_addr));if(IP == NULL){//如果這樣使用 0.0.0.0,任意ip將可以連接serv_addr.sin_addr.s_addr = INADDR_ANY;}else{if(inet_pton(AF_INET,IP,&serv_addr.sin_addr.s_addr) <= 0){perror(IP);//轉換失敗exit(1);}}serv_addr.sin_family = AF_INET;serv_addr.sin_port   = htons(port);int opt = 1;setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));Bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));return lfd;
}

wrap.h

#ifndef __WRAP_H_
#define __WRAP_H_
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);
int tcp4bind(short port,const char *IP);
#endif

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/86499.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/86499.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/86499.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Python 數據分析與可視化 Day 5 - 數據可視化入門(Matplotlib Seaborn)

&#x1f3af; 今日目標 掌握 Matplotlib 的基本繪圖方法&#xff08;折線圖、柱狀圖、餅圖&#xff09;掌握 Seaborn 的高級繪圖方法&#xff08;分類圖、分布圖、箱線圖&#xff09;熟悉圖像美化&#xff08;標題、標簽、顏色、風格&#xff09;完成一組學生成績數據的可視化…

CephFS “Client Failing to Respond to Cache Pressure“ 告警分析

告警含義 當出現 Client failing to respond to cache pressure 警告時,表明: 元數據服務器 (MDS) 要求客戶端釋放緩存的元數據(如 inode Capabilities)客戶端未能及時響應 釋放請求核心觸發機制 MDS 通過以下周期性流程管理緩存 階段操作觸發條件Cache Trim 周期每隔 mds…

生成式人工智能實戰 | 生成對抗網絡(Generative Adversarial Network, GAN)

生成式人工智能實戰 | 生成對抗網絡 0. 前言1. 生成對抗網絡2. 模型構建2.1 生成器2.2 判別器 3. 模型訓練3.1 數據加載3.2 訓練流程 0. 前言 生成對抗網絡 (Generative Adversarial Networks, GAN) 是一種由兩個相互競爭的神經網絡組成的深度學習模型&#xff0c;它由一個生成…

緩存與加速技術實踐-MongoDB數據庫應用

一.什么是MongoDB MongoDB 是一個文檔型數據庫&#xff0c;數據以類似 JSON 的文檔形式存儲。 MongoDB 的設計理念是為了應對大數據量、高性能和靈活性需求。 MongoDB 使用集合&#xff08;Collections&#xff09;來組織文檔&#xff08;Documents&#xff09;&#xff0…

聲網對話式AI把“答疑機器人”變成“有思維的助教”

作為一家專注初高中學生的線上教育平臺&#xff0c;我們精心打磨的系統化課程收獲了不少認可&#xff0c;但課后無人答疑的難題卻始終橫亙在前。學生課后遇到疑惑&#xff0c;要么只能默默憋在心里&#xff0c;要么就得苦苦等待下一節課&#xff0c;家長們也頻繁抱怨 “花了錢&…

常見的排序方法

目錄 1. 插入排序 2. 希爾排序 3. 選擇排序 4. 堆排序 5. 冒泡排序 6. 快速排序 1. 快速排序的實現 1. 思路&#xff08;以從小到大排序為例&#xff09; 2. 選取基準元素的方法&#xff08;Hoare&#xff09; 3. 選取基準元素的方法&#xff08;挖坑法&#xff09; …

【matlab定位例程】基于AOA和TDOA混合的定位方法,背景為三維空間,自適應錨點數量,附下載鏈接

文章目錄 代碼概述代碼功能概述核心算法原理AOA定位模型TDOA定位迭代算法混合定位策略關鍵技術創新 運行結果4個錨點的情況40個錨點的情況 MATLAB源代碼 代碼概述 代碼功能概述 本代碼實現了一種三維空間中的混合定位算法&#xff0c;結合到達角&#xff08; A O A AOA AOA&a…

專題:2025醫療AI應用研究報告|附200+份報告PDF匯總下載

原文鏈接&#xff1a;https://tecdat.cn/?p42748 本報告匯總解讀聚焦醫療行業人工智能應用的前沿動態與市場機遇&#xff0c;以數據驅動視角剖析技術演進與商業落地的關鍵路徑。從GenAI在醫療領域的爆發式增長&#xff0c;到細分場景的成熟度矩陣&#xff0c;再到運營成本壓力…

推薦一個前端基于vue3.x,vite7.x,后端基于springboot3.4.x的完全開源的前后端分離的中后臺管理系統基礎項目(純凈版)

XHan Admin 簡介 &#x1f389;&#x1f389; XHan Admin 是一個開箱即用的開源中后臺管理系統基礎解決方案&#xff0c; 項目為前后端分離架構。采用最新的技術棧全新構建&#xff0c;純凈的項目代碼&#xff0c;沒有歷史包袱。 前端使用最新發布的 vite7.0 版本構建&#xf…

MySQL誤刪數據急救指南:基于Binlog日志的實戰恢復詳解

背景 數據誤刪是一個比較嚴重的場景 1.典型誤操作場景 場景1&#xff1a;DELETE FROM orders WHERE status0 → 漏寫AND create_time>‘2025-06-20’ 場景2&#xff1a;DROP TABLE customer → 誤執行于生產環境 認識 binlog 1.binlog 的核心作用 記錄所有 DDL/DML 操…

高效數據采集方案:快速部署與應用 AnyCrawl 網頁爬蟲工具實操指南

以下是對 AnyCrawl 的簡單介紹&#xff1a; AnyCrawl 提供高性能網頁數據爬取&#xff0c;其功能專為 LLM 集成和數據處理而設計支持利用搜索引擎直接查詢獲取結果內容&#xff0c;類似 searxng提供開發者友好的API&#xff0c;支持動態內容抓取&#xff0c;并輸出結構化數據&…

vue3可以分頁、搜索的select

下載 npm i v-selectpage基本使用 import { SelectPageList } from v-selectpage;<SelectPageListlanguage"zh-chs"key-prop"id"label-prop"name"fetch-data"fetchData" />const fetchData (data,callback) > {const { sea…

C# 入門學習教程 (一)

文章目錄 一、解決方案與項目1. Solution 與 project 二、類與名稱空間1.類與名稱空間2.類庫的引用1. DLL引用&#xff08;黑盒引用&#xff0c;無源代碼&#xff09;2. Nuget 引用3. 項目引用&#xff08;白盒引用&#xff0c;有源代碼&#xff09; 3.依賴關系 三、類&#xf…

76、單元測試-參數化測試

76、單元測試-參數化測試 參數化測試是一種單元測試技術&#xff0c;通過將測試數據與測試邏輯分離&#xff0c;使用不同的輸入參數多次運行相同的測試用例&#xff0c;從而提高測試效率和代碼復用性。 #### 基本原理 - **數據驅動測試**&#xff1a;將測試數據參數化&#xf…

SQL學習筆記3

SQL常用函數 1、字符串函數 函數調用的語法&#xff1a;select 函數&#xff08;參數); 常用的字符串函數有&#xff1a; 拼接字符串&#xff0c;將幾個字符串拼到一起&#xff1a;concat (s1,s2,……); select concat(你好,hello); update mytable set wherefo concat(中…

Golang 面向對象編程,如何實現 封裝、繼承、多態

Go語言雖然不是純粹的面向對象語言&#xff0c;但它通過結構體(struct)、接口(interface)和方法(method)提供了面向對象編程的能力。下面我將通過具體示例展示Go中如何實現類、封裝、繼承、多態以及構造函數等概念。 1. 類與封裝 在Go中&#xff0c;使用結構體(struct)來定義…

為什么android要使用Binder機制

1.linux中大多數標準 IPC 場景&#xff08;如管道、消息隊列、ioctl 等&#xff09;的進程間通信機制 ------------------ ------------------ ------------------ | 用戶進程 A | | 內核空間 | | 用戶進程 B | | (User Spa…

OpenCV CUDA模塊設備層-----雙曲余弦函數cosh()

操作系統&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 該函數用于計算四維浮點向量&#xff08;float4類型&#xff09;的雙曲余弦值&#xff0c;作用于CUDA設備端。雙曲余弦函數定義為cosh(x) (e? …

48頁PPT | 企業數字化轉型關鍵方法論:實踐路徑、案例和落地評估框架

目錄 一、什么是企業數據化轉型&#xff1f; 二、為什么要進行數據化轉型&#xff1f; 1. 市場復雜性與不確定性上升 2. 內部流程效率與協同難題突出 3. 數字資產沉淀不足&#xff0c;智能化基礎薄弱 三、數據化流程管理&#xff1a;從“業務流程”到“數據流程”的對齊 …

VTK中的形態學處理

VTK圖像處理代碼解析:閾值化與形態學開閉運算 這段代碼展示了使用VTK進行醫學圖像處理的兩個關鍵步驟:閾值分割和形態學開閉運算。下面我將詳細解析每個部分的功能和實現原理。 處理前 處理后 1. 閾值分割部分 (vtkImageThreshold) vtkSmartPointer<vtkImageThresho…