linux进程间的网络通信
一、进程是如何进行网络通信的?socket通信的过程?
同一机器上的不同进程之间的通信方式有很多种,主要使用消息传递或共享内存。而跨网络的进程是几乎都是使用socket通信,例如web服务器,QQ。
socket即是一种特殊的文件,操作系统提供了一些socket函数就是对其进行的操作(读/写IO、打开、关闭),进程间的通信就是靠读写各自的socket完成的。
通信的过程
server:
- 使用socket()系统调用创建一个指定类型和协议套接字
- 使用bind()系统调用给创建的socket命名,这个名字就是通常所说的服务器地址(ip地址+端口号),例如服务器的80端口
- 使用listen()系统调用,监听来自客户端的连接。
- 使用accept()系统调用,接受来自客户端的连接,这个调用一直处于阻塞状态,直到有客户端的连接。
- 向连接建立的socket里面读写数据(通信)
注意:server最初会创建一个socktet,收到连接请求后(accept())之后会创建一个与原有的命名套接字不同的套接字。这个新的套接字只与这个特定的client通信,而命名套接字会保留下来继续处理来自client的连接。
client:
- 使用socket()系统调用创建一个指定类型和协议的套接字。
- 使用connect()将1中创建的socket连接到服务器的地址。
- 使用系统调用发送和接受数据,最简单的是read( )和write()函数。
二、主要知识点和系统调用介绍
1. int socket(int domain, int type, int protocol); 创建指定类型的socket,两个进程能够通信,必须使用相同域和类型的套接字。
- domain:主要有AF_INET,AF_INIT6,分别表示IPv4、IPv6域
- type:SOCK_STREAM 表示有序、可靠、双向的面向连接的字节流 ;SOCK_DGRAM 表示长度固定的、无连接的不可靠的报文传递。
- protocol:一般是0,系统会根据前面的域名和类型,选择合适的协议如TCP、UDP协议等。
2. int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd:创建scoket时返回的socket描述符。类似于文件描述符号。
- addr:socket绑定的地址
- addrlen:第二个参数是指针,第三个参数是长度
地址格式:
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 */ };
注意:通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。
这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。
3.其它
- int listen(int sockfd, int backlog); //监听socket的描述符,backlog表示最到连接数
- int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); //客户端连接服务器的地址
- int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //服务器接受连接
- read()/write()
- recv()/send()
- readv()/writev()
- recvmsg()/sendmsg()
- recvfrom()/sendto()
三、简单易学的socket程序示例
一个简单的示例,创建AF_INET型域和SOCK_STREAM面向连接的socket字,server开启服务,client请求连接,向server发送消息,server收到消息后,回应client,结束连接,也关闭服务器。
代码中有注释,基本上是安装前面socket通信的步骤写的代码。
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <sys/socket.h> 7 #include <netinet/in.h> 8 9 void error(const char *msg) 10 { 11 perror(msg); 12 exit(1); 13 } 14 15 int main(int argc, char *argv[]) 16 { 17 int sockfd, newsockfd, portno; 18 socklen_t clilen; 19 char buffer[256]; 20 struct sockaddr_in serv_addr, cli_addr; 21 int n; 22 if (argc < 2) { 23 fprintf(stderr,"ERROR, no port provided\n"); 24 exit(1); 25 } 26 27 //create a socktet 28 sockfd = socket(AF_INET, SOCK_STREAM, 0); 29 if (sockfd < 0) 30 error("ERROR opening socket"); 31 32 //bind an address to that socktet 33 bzero((char *) &serv_addr, sizeof(serv_addr)); 34 portno = atoi(argv[1]); 35 serv_addr.sin_family = AF_INET; 36 serv_addr.sin_addr.s_addr = INADDR_ANY; 37 serv_addr.sin_port = htons(portno); 38 if (bind(sockfd, (struct sockaddr *) &serv_addr, 39 sizeof(serv_addr)) < 0) 40 error("ERROR on binding"); 41 42 // listen to the socktet 43 listen(sockfd,5); 44 45 //accept connection and create a corresponding new socket 46 clilen = sizeof(cli_addr); 47 newsockfd = accept(sockfd, 48 (struct sockaddr *) &cli_addr, 49 &clilen); 50 if (newsockfd < 0) 51 error("ERROR on accept"); 52 53 54 //communication with the new sockfd(read and write data) 55 bzero(buffer,256); 56 n = read(newsockfd,buffer,255); 57 if (n < 0) error("ERROR reading from socket"); 58 printf("Here is the message: %s\n",buffer); 59 n = write(newsockfd,"I got your message",18); 60 if (n < 0) error("ERROR writing to socket"); 61 62 //ends the connection 63 close(newsockfd); 64 65 //ends server 66 close(sockfd); 67 return 0; 68 }
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <string.h> 5 #include <sys/types.h> 6 #include <sys/socket.h> 7 #include <netinet/in.h> 8 #include <netdb.h> 9 10 void error(const char *msg) 11 { 12 perror(msg); 13 exit(0); 14 } 15 16 int main(int argc, char *argv[]) 17 { 18 int sockfd, portno, n; 19 struct sockaddr_in serv_addr; 20 struct hostent *server; 21 22 char buffer[256]; 23 if (argc < 3) { 24 fprintf(stderr,"usage %s hostname port\n", argv[0]); 25 exit(0); 26 } 27 portno = atoi(argv[2]); 28 29 //create a socket 30 sockfd = socket(AF_INET, SOCK_STREAM, 0); 31 if (sockfd < 0) 32 error("ERROR opening socket"); 33 34 35 server = gethostbyname(argv[1]); 36 if (server == NULL) { 37 fprintf(stderr,"ERROR, no such host\n"); 38 exit(0); 39 } 40 bzero((char *) &serv_addr, sizeof(serv_addr)); 41 serv_addr.sin_family = AF_INET; 42 bcopy((char *)server->h_addr, 43 (char *)&serv_addr.sin_addr.s_addr, 44 server->h_length); 45 serv_addr.sin_port = htons(portno); 46 47 //connect 48 if (connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0) 49 error("ERROR connecting"); 50 printf("Please enter the message: "); 51 bzero(buffer,256); 52 fgets(buffer,255,stdin); 53 54 55 //write and read data 56 n = write(sockfd,buffer,strlen(buffer)); 57 if (n < 0) 58 error("ERROR writing to socket"); 59 bzero(buffer,256); 60 n = read(sockfd,buffer,255); 61 if (n < 0) 62 error("ERROR reading from socket"); 63 printf("%s\n",buffer); 64 65 66 //end this connection 67 close(sockfd); 68 return 0; 69 }
使用方法:./server 3000 ./client localhost 3000 一般使用2000~65536之间的端口号
上面的代码虽然能让人很快理解,进程之间的网络通信是怎么进行的。但是server只是接收一次消息,就马上结束退出。
而实际中的server是一直在运行的,并且能够同时接收多个client的访问,典型的做法是server每次收到连接请求式都fork一个新的子进程来处理连接请求。
为了避免产生zombie进程,需要在程序中使用signal(SIGCHLD,SIG_IGN);使得父进程无视子进程的die。
改进后的代码:
1 /* A simple server in the internet domain using TCP 2 The port number is passed as an argument 3 This version runs forever, forking off a separate 4 process for each connection 5 */ 6 #include <stdio.h> 7 #include <unistd.h> 8 #include <stdlib.h> 9 #include <string.h> 10 #include <sys/types.h> 11 #include <sys/socket.h> 12 #include <netinet/in.h> 13 #include <signal.h> 14 15 void dostuff(int); /* function prototype */ 16 void error(const char *msg) 17 { 18 perror(msg); 19 exit(1); 20 } 21 22 int main(int argc, char *argv[]) 23 { 24 int sockfd, newsockfd, portno, pid; 25 socklen_t clilen; 26 struct sockaddr_in serv_addr, cli_addr; 27 28 if (argc < 2) { 29 fprintf(stderr,"ERROR, no port provided\n"); 30 exit(1); 31 } 32 sockfd = socket(AF_INET, SOCK_STREAM, 0); 33 if (sockfd < 0) 34 error("ERROR opening socket"); 35 36 bzero((char *) &serv_addr, sizeof(serv_addr)); 37 portno = atoi(argv[1]); 38 serv_addr.sin_family = AF_INET; 39 serv_addr.sin_addr.s_addr = INADDR_ANY; 40 serv_addr.sin_port = htons(portno); 41 if (bind(sockfd, (struct sockaddr *) &serv_addr, 42 sizeof(serv_addr)) < 0) 43 error("ERROR on binding"); 44 45 listen(sockfd,5); 46 clilen = sizeof(cli_addr); 47 signal(SIGCHLD,SIG_IGN); 48 while (1) { 49 newsockfd = accept(sockfd, 50 (struct sockaddr *) &cli_addr, &clilen); 51 if (newsockfd < 0) 52 error("ERROR on accept"); 53 pid = fork(); 54 if (pid < 0) 55 error("ERROR on fork"); 56 if (pid == 0) { 57 //close(sockfd); 58 dostuff(newsockfd); 59 exit(0); 60 } 61 else close(newsockfd); 62 } /* end of while */ 63 close(sockfd); 64 return 0; /* we never get here */ 65 } 66 67 /******** DOSTUFF() ********************* 68 There is a separate instance of this function 69 for each connection. It handles all communication 70 once a connnection has been established. 71 *****************************************/ 72 void dostuff (int sock) 73 { 74 int n; 75 char buffer[256]; 76 77 bzero(buffer,256); 78 n = read(sock,buffer,255); 79 if (n < 0) error("ERROR reading from socket"); 80 printf("Here is the message: %s\n",buffer); 81 n = write(sock,"I got your message",18); 82 if (n < 0) error("ERROR writing to socket"); 83 }
四、总结
socket几乎是网络间进程通信的唯一手段,它的通信是非常简单并且重要的,需要记住。虽然在实际的大型的网络服务器中,不会使用每次都使用socke系统调用t编程,而是使用包装过的库,但是还是需要了解。
当让要想成为一个C++高手还是需要熟悉某种网络库,能力强的话可以自己包装实现一个网络库。但是我觉得新手还是先看看人家的库~~如:比如boost-asio、比如libevent,boost-asio
参考:
1.http://www.linuxhowtos.org/C_C++/socket.htm?userrate=2
2.http://www.cnblogs.com/skynet/archive/2010/12/12/1903949.html