linux c 網路程式設計
OSI七層網路模型由下至上為1至7層,分別為:
物理層(Physical layer),資料鏈路層(Data link layer),網路層(Network layer),傳輸層(Transport layer),會話層(Session layer),表示層(Presentation layer),應用層(Application layer)。
1.1 應用層,很簡單,就是應用程式。這一層負責確定通訊物件,並確保由足夠的資源用於通訊,這些當然都是想要通訊的應用程式乾的事情。
1.2 表示層,負責資料的編碼、轉化,確保應用層的正常工作。這一層,是將我們看到的介面與二進位制間互相轉化的地方,就是我們的語言與機器語言間的轉化。資料的壓縮、解壓,加密、解密都發生在這一層。這一層根據不同的應用目的將資料處理為不同的格式,表現出來就是我們看到的各種各樣的副檔名。
1.3 會話層,負責建立、維護、控制會話,區分不同的會話,以及提供單工(Simplex)、半雙工(Half duplex)、全雙工(Full duplex)三種通訊模式的服務。我們平時所知的NFS,RPC,X Windows等都工作在這一層。
1.4 傳輸層,負責分割、組合資料,實現端到端的邏輯連線。資料在上三層是整體的,到了這一層開始被分割,這一層分割後的資料被稱為段(Segment)。三次握手(Three-way handshake),面向連線(Connection-Oriented)或非面向連線(Connectionless-Oriented)的服務,流控(Flow control)等都發生在這一層。
1.5 網路層,負責管理網路地址,定位裝置,決定路由。我們所熟知的IP地址和路由器就是工作在這一層。上層的資料段在這一層被分割,封裝後叫做包(Packet),包有兩種,一種叫做使用者資料包(Data packets),是上層傳下來的使用者資料;另一種叫路由更新包(Route update packets),是直接由路由器發出來的,用來和其他路由器進行路由資訊的交換。
1.6 資料鏈路層,負責準備物理傳輸,CRC校驗,錯誤通知,網路拓撲,流控等。我們所熟知的MAC地址和交換機都工作在這一層。上層傳下來的包在這一層被分割封裝後叫做幀(Frame)。
1.7 物理層,就是實實在在的物理鏈路,負責將資料以位元流的方式傳送、接收,就不多說了。
3.2,TCP握手
3.2.1 三次握手建立連線
在TCP/IP協議中,TCP協議提供可靠的連線服務,採用三次握手建立一個連線。
1,客戶端向伺服器傳送一個SYN J
2,伺服器向客戶端響應一個SYN K,並對SYN J進行確認ACK J+1
3,客戶端再想伺服器發一個確認ACK K+1
從圖中可以看出,當客戶端呼叫connect時,觸發了連線請求,向伺服器傳送了SYN J包,這時connect進入阻塞狀態;伺服器監聽到連線請求,即收到SYN J包,呼叫accept函式接收請求向客戶端傳送SYN K ,ACK J+1,這時accept進入阻塞狀態;客戶端收到伺服器的SYN K ,ACK J+1之後,這時connect返回,並對SYN K進行確認;伺服器收到ACK K+1時,accept返回,至此三次握手完畢,連線建立。
三次握手的目的是建立雙向的連線,第一次握手是客戶端向伺服器端發出請求
第二次握手是伺服器端告訴客戶端,第一次握手是成功的,即可以從客戶端傳送到客戶端,
第三次握手是客戶端告訴伺服器端,第二次握手是成功的,即可以從客戶端到伺服器端
這樣就保證了客戶端和伺服器端的雙向通訊,
3.2.2 四次握手釋放連線
1,某個應用程式首先呼叫close主動關閉連線,這時TCP傳送一個FIN M;
2,另一端接收到FIN M之後,執行被動關閉,對這個FIN進行確認。它的接收也作為檔案結束符傳遞給應用程式,因為FIN的接收意味著應用程式在相應的連線上再也接收不到額外資料;
3,一段時間之後,接收到檔案結束符的應用程式呼叫close關閉它的socket。這導致它的TCP也傳送一個FIN N;
4,接收到這個FIN的源傳送端TCP對它進行確認。
3.3,socket程式設計
http://www.cnblogs.com/goodcandle/archive/2005/12/10/socket.html
先從伺服器端說起。伺服器端先初始化Socket,然後與埠繫結(bind),對埠進行監聽(listen),呼叫accept阻塞,等待客戶端連線。在這時如果有個客戶端初始化一個Socket,然後連線伺服器(connect),如果連線成功,這時客戶端與伺服器端的連線就建立了。客戶端傳送資料請求,伺服器端接收請求並處理請求,然後把迴應資料傳送給客戶端,客戶端讀取資料,最後關閉連線,一次互動結束。
3.3.1 AF_INET TCP傳輸最簡單版本
tcp_client.c
- #include <unistd.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <errno.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #define PORT 6666
- #define BUFF_SIZE 1024
- #define MAXLEN 4096
- main(int argc, char** argv)
- {
- if(argc!=2){
- printf("usage: ./client <ipaddress>\n");
- exit(0);
- }
- char sendline[4096];
- //socket()建立socket
- int sockfd = socket(AF_INET,SOCK_STREAM,0);
- //要連線的伺服器地址
- struct sockaddr_in sliaddr;
- sliaddr.sin_family = AF_INET;
- sliaddr.sin_port = htons(PORT);
- inet_pton(AF_INET, argv[1], &sliaddr.sin_addr);
- //connect()傳送請求(ip=argv[1],protocal=TCP,port=6666)
- connect(sockfd, (struct sockaddr*)&sliaddr, sizeof(sliaddr));
- //recv sth
- recv_len = recv(sockfd, buff, sizeof(buff), 0);
- buff[recv_len] = '\0';
- printf(" %s ", buff);
- //interactive
- while (1)
- {
- printf("Enter string to send: ");
- scanf("%s", buff);
- if (!strcmp(buff, "quit"))
- break;
- send_len = send(sockfd, buff, strlen(buff), 0);
- recv_len = recv(sockfd, buff, BUFF_SIZE, 0);
- buff[recv_len] = '\0';
- printf(" received: %s \n", buff);
- }
- //close()關閉連線
- close(sockfd);
- }
tcp_server.c
- #include <unistd.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <errno.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #define PORT 6666
- #define WAIT_QUEUE_LEN 5
- #define BUFF_SIZE 1024
- #define WELCOME "Welcome to my server ^_^!\n"
- main()
- {
- int connfd;
- char buff[MAXLEN];
- int len;
- //socket() 建立socket,其中SOCK_STREAM表示tcp連線
- int sockfd = socket(AF_INET,SOCK_STREAM,0);
- struct sockaddr_in servaddr;
- servaddr.sin_family = AF_INET;
- servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
- servaddr.sin_port = htons(PORT);
- //bind()繫結一個socket(ip=all,protocal=TCP,port=6666)
- bind(sockfd,(struct sockaddr *) &servaddr, sizeof(servaddr));
- //listen()監聽
- listen(sockfd, WAIT_QUEUE_LEN);
- //accept() & close()
- printf("======waiting for client's request======\n");
- while(1){
- c_addrlen = sizeof(struct sockaddr_in);
- connfd = accept(serverfd, (struct sockaddr *)&caddr, &c_addrlen);
- printf("client: ip=%s,port=%s\n", cliaddr.sin_addr.s_addr,cliaddr.sin_port);
- //send a welcome
- send(connfd, WELCOME, strlen(WELCOME), 0);
- //阻塞模式下recv==0表示客戶端已斷開連線
- while ((len = recv(connfd, buff, BUFF_SIZE, 0)) > 0)
- {
- buff[len] = '\0';
- printf("recv msg is : %s \n", buff);
- send(connfd, buff, len, 0);
- }
- close(connfd);
- }
- //close()關閉連線
- close(sockfd);
- }
<0 出錯
=0 連線關閉
>0 接收到資料大小,
makefile
- .PHONY : main
- main : server client
- server : server.o
- gcc -g -o server server.o
- client : client.o
- gcc -g -o client client.o
- server.o : server.c
- gcc -g -c server.c
- client.o : client.c
- gcc -g -c client.c
- clean :
- rm -rf *.o
- ser :
- ./server
- cli :
- ./client
3.3.2 加入返回值檢查和IP地址
tcp_client.c
- #include <unistd.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <errno.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #define MAXLEN 4096
- main(int argc, char** argv)
- {
- if(argc!=2){
- printf("usage: ./client <ipaddress>\n");
- exit(0);
- }
- char sendline[4096];
- //socket()建立socket
- int sockfd;
- if((sockfd=socket(AF_INET,SOCK_STREAM,0)) ==-1){
- printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
- }
- struct sockaddr_in cliaddr;
- cliaddr.sin_family = AF_INET;
- cliaddr.sin_port = htons(6666);
- if(inet_pton(AF_INET, argv[1], &cliaddr.sin_addr)==-1){
- printf("inet_pton error for %s\n",argv[1]);
- exit(0);
- }
- //connect()傳送請求(ip=argv[1],protocal=TCP,port=6666)
- if(connect(sockfd, (struct sockaddr*)&cliaddr, sizeof(cliaddr))==-1){
- printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
- exit(0);
- }
- printf("send msg to server: \n");
- fgets(sendline, 4096, stdin);
- //send()傳送資料
- if(send(sockfd, sendline, strlen(sendline), 0)==-1){
- printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
- exit(0);
- }
- //close()關閉連線
- close(sockfd);
- }
tcp_server.c
- #include <unistd.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <errno.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #define MAXLEN 4096
- main()
- {
- int connfd;
- char buff[MAXLEN];
- int n;
- //socket()建立socket
- int sockfd;
- if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){
- printf("create socket error: %s(errno:%d)\n",strerror(errno),errno);
- exit(0);
- }
- struct sockaddr_in servaddr;
- servaddr.sin_family = AF_INET;
- servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
- servaddr.sin_port = htons(6666);
- //bind()繫結一個socket(ip=all,protocal=TCP,port=6666)
- if(bind(sockfd,(struct sockaddr *) &servaddr, sizeof(servaddr))==-1){
- printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
- exit(0);
- }
- //listen()監聽
- if(listen(sockfd,10)==-1){
- printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
- exit(0);
- }
- //accept() & close()
- printf("======waiting for client's request======\n");
- while(1){
- if((connfd=accept(sockfd, (struct sockaddr *)NULL,NULL))==-1){
- printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
- continue;
- }
- struct sockaddr_in serv, guest;
- char serv_ip[20];
- char guest_ip[20];
- int serv_len = sizeof(serv);
- int guest_len = sizeof(guest);
- getsockname(connfd, (struct sockaddr *)&serv, &serv_len);
- getpeername(connfd, (struct sockaddr *)&guest, &guest_len);
- inet_ntop(AF_INET, &serv.sin_addr, serv_ip, sizeof(serv_ip));
- inet_ntop(AF_INET, &guest.sin_addr, guest_ip, sizeof(guest_ip));
- printf("host %s:%d guest %s:%d\n", serv_ip, ntohs(serv.sin_port), guest_ip, ntohs(guest.sin_port));
- n = recv(connfd, buff, MAXLEN,0);
- buff[n] = '\0';
- printf("recv msg from client: %s\n", buff);
- close(connfd);
- }
- //close()關閉連線
- close(sockfd);
- }
3.3.3 AF_INET UDP傳輸最簡單版本
udp_client.c
- /**
- * @file: udpclient.c
- * @brief: A simple Udp server
- * @author: ToakMa <mchgloak1120@163.com>
- * @date: 2014/10/09
- */
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <strings.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #define BUFF_SIZE 1024
- #define PORT 9988
- int main(int argc, char *argv[])
- {
- int sockfd;
- struct sockaddr_in remote_addr;
- int len;
- char buff[BUFF_SIZE];
- //1. create a socket
- sockfd = socket(AF_INET, SOCK_DGRAM, 0);
- if (-1 == sockfd)
- {
- perror("udp client socket: ");
- return -1;
- }
- //2. prepare ip and port
- memset(&remote_addr, 0, sizeof(remote_addr));
- remote_addr.sin_family = AF_INET;
- remote_addr.sin_port = htons(PORT);
- remote_addr.sin_addr.s_addr = inet_addr(argv[1]);
- bzero(&(remote_addr.sin_zero), 8);
- //3. sendto
- strcpy(buff, "this a test\n");
- printf("sending : %s\n", buff);
- len = sendto(sockfd, buff, strlen(buff), 0, (struct sockaddr *)&remote_addr, sizeof(remote_addr));
- if (len < 0)
- {
- perror("udp client sendto :");
- return -1;
- }
- //4. close
- close(sockfd);
- return 0;
- }
udp_server.c
- /**
- * @file: udpserver.c
- * @brief: A simple Udp server
- * @author: ToakMa <mchgloak1120@163.com>
- * @date: 2014/10/09
- */
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- #include <strings.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #define PORT 9988
- #define BUFF_SIZE 1024
- int main(int argc, char *argv[])
- {
- int sockfd;
- int sin_len;
- struct sockaddr_in saddr;
- struct sockaddr_in remote_addr;
- char buff[BUFF_SIZE];
- int res, len;
- //1. create socket
- sockfd = socket(AF_INET, SOCK_DGRAM, 0);
- if (-1 == sockfd)
- {
- perror("Udp server socket: ");
- return -1;
- }
- printf("Udp server socket create succ!\n");
- //2. prepare IP and port
- memset(&saddr, 0, sizeof(saddr));
- saddr.sin_family = AF_INET;
- saddr.sin_port = htons(PORT);
- saddr.sin_addr.s_addr = INADDR_ANY;
- bzero(saddr.sin_zero, 8);
- //3. bind
- res = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
- if (-1 == res)
- {
- perror("udp server bind: ");
- return -1;
- }
- //4. recvfrom
- printf("Wait for a packet ...\n");
- sin_len = sizeof(struct sockaddr_in);
- len = recvfrom(sockfd, buff, BUFF_SIZE, 0, (struct sockaddr *)&remote_addr, &sin_len);
- if (-1 == len)
- {
- perror("udp server recvform: ");
- return -1;
- }
- buff[len] = '\0';
- printf("Recived packet from %s, contents is: %s \n", \
- inet_ntoa(remote_addr.sin_addr), buff);
- //5. close
- close(sockfd);
- return 0;
- }
3.3.4 AF_INET UNIX本地傳輸最簡單版本
unix_client.c
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <stdio.h>
- #include <sys/un.h>
- #include <unistd.h>
- #include <errno.h>
- int main()
- {
- int result;
- char ch = 'A';
- //建立socket
- int sockfd;
- if((sockfd = socket(AF_UNIX,SOCK_STREAM,0) == -1){
- printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
- }
- struct sockaddr_un address;
- address.sun_family = AF_UNIX;
- strcpy(address.sun_path,"server_socket");
- int len = sizeof(address);
- //connect()
- int result;
- if((result = connect(socket,(struct sockaddr*)&address,len)==-1){
- printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
- exit(0);
- }
- //read & write
- write(sockfd,&ch,1);
- read(sockfd,&ch,1);
- printf("char from server = %c\n",ch);
- //close()
- close(sockfd);
- exit(0);
- }
unix_server.c
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <stdio.h>
- #include <sys/un.h>
- #include <unistd.h>
- #include <errno.h>
- int main()
- {
- int server_len,client_len;
- struct socket_un server_address;
- struct socket_un client_address;
- //刪除以前套接字
- unlink("server_socket");
- //socket
- int server_sockfd = socket(AF_UNIX,SOCK_STREAM,0);
- server_address.sun_family = AF_UNIX;
- strcpy(server_address.sun_path,"server_socket");
- server_len = sizeof(server_address);
- //bind()
- bind(server_sockfd,(struct sockaddr*)&server_address,server_len);
- //listen()
- listen(server_sockfd,5);
- while(1){
- char ch;
- printf("server waiting\n");
- client_len = sizeof(client_address);
- client_sockfd = accept(server_sockfd,(struct sockaddr*)&client_address,&client_len);
- //read & write
- read(client_sockfd,&ch,1);
- ch++;
- write(client_sockfd,&ch,1);
- close(client_sockfd);
- }
- }
3.4 網路程式設計函式
Unix/Linux基本哲學之一就是“一切皆檔案”,都可以用“開啟open –> 讀寫write/read –> 關閉close”
- #include<sys/types.h>
- #include<sys/socket.h>
3.4.1 函式socket
- 函式原型:
- int socket(int domain, int type, int protocol);
- 引數說明:
- domain:即協議域。常用的協議族有,AF_INET(ipv4)、AF_INET6(ipv6)、AF_LOCAL(或稱AF_UNIX,Unix域socket)、AF_ROUTE等等。
- type:指定socket型別。常用的socket型別有,SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。
- protocol:指定協議。常用的協議有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等。
- int:返回值為-1表示出錯
3.4.2 函式bind
命名套接字:bind()函式把一個地址族中的特定地址賦給socket。例如對應AF_INET、AF_INET6就是把一個ipv4或ipv6地址和埠號組合賦給socket。
- 函式原型:
- int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 引數說明:
- sockfd:是由socket()呼叫返回的套介面檔案描述符。
- addr:傳入資料結構sockaddr的指標,包括(IP,protocol,port)需要轉換為通用地址型別struct sockaddr*
- addrlen:以設定成sizeof(struct sockaddr)
- int:返回值,-1表示出錯
- ipv4地址結構【AF_INET】:
- struct sockaddr_in {
- sa_family_t sin_family; /* address family: AF_INET */
- in_port_t sin_port; /* port in network byte order */
- struct in_addr sin_addr; /* internet address */
- };
- /* Internet address. */
- struct in_addr {
- uint32_t s_addr; /* address in network byte order */
- };
- struct sockaddr_in6 {
- sa_family_t sin6_family; /* AF_INET6 */
- in_port_t sin6_port; /* port number */
- uint32_t sin6_flowinfo; /* IPv6 flow information */
- struct in6_addr sin6_addr; /* IPv6 address */
- uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */
- };
- struct in6_addr {
- unsigned char s6_addr[16]; /* IPv6 address */
- };
- #define UNIX_PATH_MAX 108
- struct sockaddr_un {
- sa_family_t sun_family; /* AF_UNIX */
- char sun_path[UNIX_PATH_MAX]; /* pathname */ 使用strcpy(address.sun_path,"server_socket")
- };
而客戶端就不用指定,有系統自動分配一個埠號和自身的ip地址組合,在connect()時由系統隨機生成一個。由於客戶端不需要固定的埠號,因此不必呼叫bind(),客戶端的埠號由核心自動分配。注意,客戶端不是不允許呼叫bind(),只是沒有必要呼叫
bind()固定一個埠號,伺服器也不是必須呼叫bind(),但如果伺服器不呼叫bind(),核心會自動給伺服器分配監聽埠,每次啟動伺服器時埠號都不一樣,客戶端要連線伺服器就會遇到麻煩。
對server端的addr的初始化如下所示:
- memset(&servaddr, 0, sizeof(servaddr));
- servaddr.sin_family = AF_INET;
- servaddr.sin_port = htons(5188);
- servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
3.4.3 函式listen
- 函式原型:
- int listen(int sockfd, int backlog);
- 引數說明:
- sockfd:即為要監聽的socket描述字
- backlog:相應socket可以排隊的最大連線個數
第二個引數是進入佇列中允許的連線的個數。進入的連線請求在使用系統呼叫accept()應答之前要在進入佇列中等待。這個值是佇列中最多可以擁有的請求的個數。大多數系統的預設設定為20。你可以設定為5或者10。當出錯時,listen()將會返回-1值。
這個函式對於backlog的解釋《unix網路程式設計》P85是說已完成連線佇列(ESTABLISHED)和未完成連線佇列(SYN_RCVD)之和(即等待連線數而非連線數)。伺服器呼叫的accept()返回並接受這個連線,如果有大量的客戶端發起連線而伺服器來不及處理,尚未accept的客戶端就處於連線等待狀態,listen()宣告sockfd處於監聽狀態,並且最多允許有backlog個客戶端處於連線等待狀態,如果接收到更多的連線請求就忽略。在計算機早期由於網路連線的處理速度很慢,幾個併發的請求就可能導致系統處理不過來而引發錯誤,現在這個數字的意義已經不大了,現在軟硬體效能幾乎能保證這個佇列不可能滿。連線的狀態變為ESTABLISHED之後(實際是連線由從半連線佇列轉移到了完成握手的完成佇列)才表示可以被accpet()處理。
3.4.4 函式accept
- 函式原型:
- int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 引數說明:
- sockfd:是由socket()呼叫返回的套介面檔案描述符。
- addr:傳出連線客戶的sockaddr指標,包括(IP,protocol,port)
- addrlen:傳入指定客戶結構addr的長度,返回時該值傳出為連線客戶地址addr的實際長度
- int:返回值,-1表示出錯
呼叫accept()之後,將會返回一個全新的套介面檔案描述符來處理這個單個的連線。這樣,對於同一個連線來說,你就有了兩個檔案描述符。原先的一個檔案描述符正在監聽你指定的埠,新的檔案描述符可以用來呼叫send()和recv()。
可以通過對套接字檔案描述符設定O_NONBLOCK來改變其是否阻塞:
- int flags = fcntl(socket, F_GETFL,0);
- fcntl(socket, F_SETFL, O_NONBLOCK| flags);
3.4.5 函式connect
- 函式原型:
- int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
3.4.6 函式read與write
read()/write()
recv()/send()
readv()/writev()
recvmsg()/sendmsg()
recvfrom()/sendto()
- #include <unistd.h>
- ssize_t read(int fd, void *buf, size_t count);
- ssize_t write(int fd, const void *buf, size_t count);
- #include <sys/types.h>
- #include <sys/socket.h>
- ssize_t send(int sockfd, const void *buf, size_t len, int flags);
- ssize_t recv(int sockfd, void *buf, size_t len, int flags);
- ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
- const struct sockaddr *dest_addr, socklen_t addrlen);
- ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
- struct sockaddr *src_addr, socklen_t *addrlen);
- ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
- ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
3.4.7 函式close
- 函式原型:
- int close(int fd);
注意:close操作只是使相應socket描述字的引用計數-1,只有當引用計數為0的時候,才會觸發TCP客戶端向伺服器傳送終止連線請求。
3.4.8 函式getsockname
函式返回與套介面關聯的本地協議地址。
- 函式原型:
- int getsockname(int sockfd, struct sockaddr * localaddr, socken_t * addrlen);
3.4.9 函式getpeername
- 函式原型:
- int getpeername(int sockfd,struct sockaddr* peeraddr,int* addrlen);
- 引數說明:
- sockfd:是由socket()呼叫返回的套介面檔案描述符。
- peeraddr:傳出資料結構sockaddr的指標,包括(IP,protocol,port)
- addrlen:傳出結構大小的指標
- int:返回值,-1表示出錯
3.5 網路位元組序與主機位元組序轉換
3.5.1 函式htonl
- uint32_t htonl(uint32_t hostlong);
3.5.2 函式htons
- uint16_t htons(uint16_t hostshort);
3.5.3 函式ntohl
- uint32_t ntohl(uint32_t netlong);
3.5.4 函式ntohs
- uint16_t ntohs(uint16_t netshort);
3.6 IP地址與主機位元組序轉換
3.6.1 函式inet_pton
[將“點分十進位制” -> “整數”]
- 函式原型:
- int inet_pton(int af, const char *src, void *dst);
- 引數說明:
- af:地址族,AF_INET為ipv4地址,AF_INET6為ipv6地址
- src:為ip地址(ipv4例如1.1.1.1)
- dst:函式將該地址轉換為in_addr的結構體,並複製在*dst中
- int:返回值:如果函式出錯將返回一個負值,並將errno設定為EAFNOSUPPORT,如果引數af指定的地址族和src格式不對,函式將返回0。
3.6.2 函式inet_ntop
[將“整數” -> “點分十進位制”]
- 函式原型:
- const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt);
3.7 主機資料庫函式
3.7.1 函式gethostname
它返回(本地)計算機的名字,儲存在hostname中,大小為size
- 函式原型:
- int gethostname(char*hostname,size_tsize);
- 引數說明:
- int:返回值gethostname將返回0。如果失敗,它將返回-1。
3.7.2 函式gethostbyname
根據域名或者主機名獲取資訊
- 函式原型:
- struct hostent *gethostbyname(const char *name);
- 引數說明:
- name:主機名,如"www.baidu.com"
- hostend:返回值。如果函式呼叫失敗,將返回NULL。
hostend的結構如下:
- struct hostent
- {
- char *h_name; //表示的是主機的規範名,例如www.google.com的規範名其實是www.l.google.com
- char **h_aliases; //表示的是主機的別名
- int h_addrtype; //IP地址的型別
- int h_length; //IP地址的長度
- char **h_addr_list; //主機的ip地址,注意這是以網路位元組順序儲存的一個值,用inet_ntop恢復
- };
示例程式碼:
- #include <netdb.h>
- #include <sys/socket.h>
- #include <stdio.h>
- #include <unistd.h>
- int main(int argc, char **argv)
- {
- char *host,**names;
- struct hostent *hostinfo;
- cha str[32];
- /* 取得命令後第一個引數,即要解析的域名或主機名 */
- if(argc==1){
- char myname[256];
- gethostname(myname,255);
- host=myname;
- }
- else{
- host=argv[1];
- }
- /* 呼叫gethostbyname()。呼叫結果都存在hostinfo中 */
- if( (hostinfo = gethostbyname(host) ) == NULL )
- {
- printf("gethostbyname error for host:%s/n", host);
- exit(1); /* 如果呼叫gethostbyname發生錯誤,返回1 */
- }
- /* 將主機的規範名打出來 */
- printf("official hostname:%s/n",hostinfo->h_name);
- /* 主機可能有多個別名,將所有別名分別打出來 */
- for(names = hostinfo->h_aliases; *names != NULL; names++)
- printf(" alias:%s/n",*names);
- /* 根據地址型別,將地址打出來 */
- switch(hostinfo->h_addrtype)
- {
- case AF_INET:
- case AF_INET6:
- names=hostinfo->h_addr_list;
- /* 將剛才得到的所有地址都打出來。其中呼叫了inet_ntop()函式 */
- for(;*names!=NULL;names++)
- printf(" address:%s/n", inet_ntop(hostinfo->h_addrtype, *names, str, sizeof(str)));
- break;
- default:
- printf("unknown address type/n");
- break;
- }
- exit(0);
- }
3.7.3 函式gethostbyaddr
根據ip地址(網路位元組序)獲取資訊
- 函式原型:
- struct hostent *gethostbyaddr(const char *name,int len,int type)
- 引數說明:
- name:ip地址,例如: inet_addr("192.168.4.111")
- len:
- type:
- hostend:返回值。如果函式呼叫失敗,將返回NULL。
3.7.4 函式getservbyname
用於根據給定的名字來查詢相應的伺服器,返回對應於給定服務名和協議名的相關服務資訊
- 函式原型:
- struct servernt *gerservbyname(const char *servname,const char *protoname)
- 引數說明:
- servname:例如smtp
- protoname:例如tcp
servent結構如下:
- struct servent{
- char *s_name; /*服務的正規名字*/
- char **s_aliases;/*別名列表*/
- int s_port; /*服務的埠號*/
- char *s_proto; /*服務的協議,如tcp或udp*/
- }
3.7.5 函式getservbyport
返回與給定服務名對應的包含名字和服務號資訊的servent結構指標(注意引數port的值必須是網路位元組序型別的,所以在使用的時候需要使用函式htons(port)來進行轉換)。
- 函式原型:
- struct servent *getservbyport(int port,const char *proroname);
- 函式示例:
- sptr=getservbyport(htons(53),"udp");
示例程式碼:
- #獲取任意已知主機的日期和時間
- #include <netdb.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <stdio.h>
- #include <unistd.h>
- int main(int argc, char **argv)
- {
- char *host;
- struct hostent * hostinfo;
- struct servent * servinfo;
- /* 取得命令後第一個引數,即要解析的域名或主機名 */
- if(argc==1){
- host="localhost";
- }
- else{
- host=argv[1];
- }
- /* 呼叫gethostbyname()。呼叫結果都存在hostinfo中 */
- if( (hostinfo = gethostbyname(host) ) == NULL )
- {
- printf("gethostbyname error for host:%s/n", host);
- exit(1); /* 如果呼叫gethostbyname發生錯誤,返回1 */
- }
- if ( (servinfo = getservbyname("daytime","tcp")) );
- {
- printf("no daytime service/n");
- exit(1); /* 如果呼叫getservbyname發生錯誤,返回1 */
- }
- /* 將daytime的埠列印出來*/
- printf("daytime port is %d/n",ntohs(servinfo -> s_port));
- int sockfd = socket(AF_INET, SOCK_STREAM,0);
- struct sockaddr_in address;
- address.sin_family = AF_INET;
- address.sin_port = servinfo -> s_port;
- address.sin_addr= *(struct in_addr*)*hostinfo->h_addr_list;
- int len = sizeof(address);
- int result;
- if((result = connect(socket,(struct sockaddr*)&address,len))==-1){
- printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
- exit(0);
- }
- char buffer[128];
- result = read(sockfd,buffer,sizeof(buffer));
- buffer[result]='\0';
- printf("read %d bytes: %s",result,buffer);
- close(sockfd);
- exit(0);
- }
3.5 多客戶程式設計
http://blog.csdn.net/simba888888/article/details/9034407
3.5.1 阻塞與非阻塞
(1)阻塞block
所謂阻塞方式block,顧名思義,就是程式或是執行緒執行到這些函式時必須等待某個事件的發生,如果事件沒有發生,程式或執行緒就被阻塞(程式Sleep),函式不能立即返回。
例如socket程式設計中connect、accept、recv、recvfrom這樣的阻塞程式。
再如絕大多數的函式呼叫、語句執行,嚴格來說,他們都是以阻塞方式執行的。
(2)非阻塞non-block
所謂非阻塞方式non-block,就是程式或執行緒執行此函式時不必非要等待事件的發生,一旦執行肯定返回,以返回值的不同來反映函式的執行情況,如果事件發生則與阻塞方式相同,若事件沒有發生則返回一個程式碼來告知事件未發生,而程式或執行緒繼續執行,所以效率較高。
非阻塞I/O有一個缺點,如果所有裝置都一直沒有資料到達,呼叫者需要反覆查詢做無用功,如果阻塞在那裡,作業系統可以排程別的程式執行,就不會做無用功了。?????
3.5.2 函式select
- 函式原型:
- int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
- 引數說明:
- nfds:為檔案描述符集合中的最大值+1,避免函式檢查fd_set的所有1024位
- readfds:被監控是否可以讀
- writefds:被監控是否可以寫
- exceptfds:被監控是否發生異常
- timeout:超時時間,Linux返回時會被修改成剩餘的時間。
- int:返回值,返回狀態發生變化的描述符的總數,錯誤返回-1,超時返回0。
關於timeout:
- struct timeval結構如下:
- struct timeval
- {
- long tv_sec; //seconds
- long tv_usec; //microseconds
- };
2,若timeout設定為t=0,表示非阻塞,函式檢查完每個fd後立即返回。
3,若timeout設定為t="NULL",表示阻塞,直到有一個fd位被置為1函式才返回。
- 相關操作:
- FD_ZERO(fd_set *set); //fd_set很多系統實現為bit arrays,將所有的檔案描述符從fd_set中清空
- FD_CLR(int fd, fd_set *set); //從set中清除fd
- FD_SET(int fd, fd_set *set); //將fd新增到set中
- FD_ISSET(int fd, fd_set *set);//判斷描述符fd是否在給定的描述符集set中,通常配合select函式使用,由於select函式成功返回時會將未準備好的描述符位清零。通常我們使用FD_ISSET是為了檢查在select函式返回後,某個描述符是否準備好,以便進行接下來的處理操作。
常用程式碼:
- fd_set rdfds;
- struct timeval tv;
- tv.tv_sec = 1;
- tv.tv_uses = 500;
- int ret;
- FD_ZERO(&rdfds);
- FD_SET(socket, &rdfds);
- ret = select (socket + 1, %rdfds, NULL, NULL, &tv);
- if(ret < 0)
- perror ("select");
- else if (ret == 0)
- printf("time out");
- else {
- printf(“ret = %d/n”,ret);
- if(FD_ISSET(socket, &rdfds)){
- /* 讀取socket控制程式碼裡的資料 */
- recv( );
- }
- }
3.5.3 函式pselect
- 函式原型:
- #include <sys/select.h>
- int pselect(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds,fd_set *restrict exceptfds, const struct timespec *restrict tsptr,const sigset_t *restrict sigmask);
- 引數說明
- 返回值:Returns: count of ready descriptors, 0 on timeout, -1 on error
1,pselect使用timespec結構指定超時值。timespec結構以秒和納秒錶示時間,而非秒和微秒。
2,pselect的超時值被宣告為const,這保證了呼叫pselect不會改變timespec結構。
3,pselect可使用一個可選擇的訊號遮蔽字。在呼叫pselect時,以原子操作的方式安裝該訊號遮蔽字,在返回時恢復以前的訊號遮蔽字。
3.5.4 函式poll
- 函式原型:
- #include <sys/poll.h>
- int poll (struct pollfd *fds, unsigned int nfds, int timeout);
- 引數說明:
- timeout:【值=INFTIM】表示永遠等待【值=0】表示立即返回,不阻塞程式【值>0】等待指定數目的毫秒數。
pollfd結構說明:
- struct pollfd {
- int fd; /* 檔案描述符 */
- short events; /* 等待的事件 */
- short revents; /* 發生的事件 */
- };
- poll函式可用的測試值:
- 常量 說明
- POLLIN 普通或優先順序帶資料可讀
- POLLRDNORM 普通資料可讀
- POLLRDBAND 優先順序帶資料可讀
- POLLPRI 高優先順序資料可讀
- POLLOUT 普通資料可寫
- POLLWRNORM 普通資料可寫
- POLLWRBAND 優先順序帶資料可寫
- POLLERR 發生錯誤
- POLLHUP 發生掛起
- POLLNVAL 描述字不是一個開啟的檔案
- 注意:後三個只能作為描述字的返回結果儲存在revents中,而不能作為測試條件用於events中。
3.5.5 函式epoll
一共有三個函式:
- int epoll_create(int size);
- int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
EPOLL_CTL_ADD:註冊新的fd到epfd中;
EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件;
EPOLL_CTL_DEL:從epfd中刪除一個fd;
第三個引數是需要監聽的fd,第四個引數是告訴核心需要監聽什麼事。
struct epoll_event結構如下:
- struct epoll_event {
- __uint32_t events; /* Epoll events */
- epoll_data_t data; /* User data variable */
- };
EPOLLIN :表示對應的檔案描述符可以讀(包括對端SOCKET正常關閉);
EPOLLOUT:表示對應的檔案描述符可以寫;
EPOLLPRI:表示對應的檔案描述符有緊急的資料可讀(這裡應該表示有帶外資料到來);
EPOLLERR:表示對應的檔案描述符發生錯誤;
EPOLLHUP:表示對應的檔案描述符被結束通話;
EPOLLET: 將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來說的。
EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL佇列裡
- int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
在前面講過的客戶/伺服器程式中,伺服器只能處理一個客戶端的請求,如何同時服務多個客戶端呢?在未講到select/poll/epoll等高階IO之前,比較老土的辦法是使用fork來實現。網路伺服器通常用fork來同時服務多個客戶端,父程式專門負責監聽埠,每次accept一個新的客戶端連線就fork出一個子程式專門服務這個客戶端。但是子程式退出時會產生殭屍程式,父程式要注意處理SIGCHLD訊號和呼叫wait清理殭屍程式,最簡單的辦法就是直接忽略SIGCHLD訊號。
1,這裡為什麼會有殭屍程式?
2,實現P2P
3,signal(SIGCHLD, SIG_IGN);
相關文章
- linux c網路網路程式設計面試題收集Linux程式設計面試題
- 【Linux】 Linux網路程式設計Linux程式設計
- Linux Socket C語言網路程式設計:TCP SocketLinuxC語言程式設計TCP
- Linux Socket C語言網路程式設計:UDP SocketLinuxC語言程式設計UDP
- Linux Socket C語言網路程式設計:Select SocketLinuxC語言程式設計
- 【Linux網路程式設計】網路程式設計常見概念Linux程式設計
- UDP&TCP Linux網路應用程式設計詳解UDPTCPLinux程式設計
- Linux網路程式設計(2)Linux程式設計
- Linux網路程式設計(1)Linux程式設計
- 網路通訊程式設計程式設計
- py網路工具程式設計程式設計
- Linux C/C++程式設計中的多執行緒程式設計基本概念LinuxC++程式設計執行緒
- python 網路篇(網路程式設計)Python程式設計
- C# .NET網路程式設計C#程式設計
- Linux C++ 多執行緒程式設計LinuxC++執行緒程式設計
- 計算機---Linux作業系統---C語言---C程式設計---微控制器---計算機網路---電腦保安---資訊保安Linux作業系統C語言C程式程式設計計算機網路
- Linux Socket C語言網路程式設計:Pthread Socket [code from GitHub, for study]LinuxC語言程式設計threadGithub
- Linux Socket C語言網路程式設計:Poll Socket [code from GitHub, for study]LinuxC語言程式設計Github
- Linux Socket C語言網路程式設計:Epoll Socket [code from GitHub, for study]LinuxC語言程式設計Github
- Linux 高效能伺服器程式設計- Linux 網路程式設計基礎 APILinux伺服器程式設計API
- 初識C#網路程式設計C#程式設計
- Linux網路程式設計之IO模型Linux程式設計模型
- 【Linux網路程式設計】位元組序Linux程式設計
- 【Linux網路程式設計-1】訊號Linux程式設計
- 【Linux網路程式設計-3】多程序Linux程式設計
- 《Linux網路開發必學教程》6_Window 下的網路程式設計Linux程式設計
- Linux網路設定Linux
- 網路程式設計-計算機網路三要素程式設計計算機網路
- 《Linux網路開發必學教程》1_網路程式設計核心概念與模式Linux程式設計模式
- 《Unix 網路程式設計》05:TCP C/S 程式示例程式設計TCP
- linux程式全解-3.4.linux應用程式設計和網路程式設計第4部分Linux程式設計
- 【Linux網路程式設計】Socket Api函式Linux程式設計API函式
- UDP協議網路Socket程式設計(java實現C/S通訊案例)UDP協議程式設計Java
- Linux學習路線及網路程式設計經典書籍(轉載)Linux程式設計
- python網路-Socket之TCP程式設計(26)PythonTCP程式設計
- C程式設計題C程式程式設計
- c#程式設計C#程式設計
- Linux jpeg程式設計Linux程式設計
- Linux Bash程式設計Linux程式設計