本文共 11152 字,大约阅读时间需要 37 分钟。
之前说的用于进程间通信的几种方式:消息signal、管道pipe、消息队列msg、共享内存shm、信号量sem。都只适用于一台主机上的进程间通信,那么如何实现两台计算机之间的进程通信呢?所以,来了解一下异地进程通信。
以下是OSI参考模型与TCP/IP参考模型的对应关系:
TCP/IP 协议组大体上分为三部分:
1.Internet 协议(IP)2.传输控制协议(TCP)和用户数据报文协议(UDP)3.处于TCP 和UDP 之上的一组协议专门开发的应用程序。它们包括:TELNET,文件传送协议(FTP),域名服务(DNS)和简单的邮件传送程序(SMTP)等许多协议。应用层协议流式套接字(SOCK_STREAM)
流式的套接字可以提供可靠的、面向连接的通讯流。它使用了TCP协议。TCP 保证了数据传输的正确性和顺序性。数据报套接字(SOCK_DGRAM)数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错。使用数据报协议UDP协议。原始套接字。原始套接字允许对低层协议如IP或ICMP直接访问,主要用于新的网络协议实现的测试等。TCP
UDP具体函数的用法,就自己man了。重点讲一下套接字地址结构:
#include < netinet/in.h>struct sockaddr{unsigned short sa_family; /* address族, AF_xxx */char sa_data[14]; /* 14 bytes的协议地址 */};
#include < netinet/in.h>struct sockaddr_in {short int sin_family; /* Internet地址族 */unsigned short int sin_port; /* 端口号 */struct in_addr sin_addr; /* Internet地址 */unsigned char sin_zero[8]; /* 添0(和struct sockaddr一样大小)*/};
-linux提供将点分格式的地址转于长整型数之间的转换函数。
inet_ntoa()能够把网络字节顺序转换为地址结构的数据。
socket() bind() connect()
listen() accept() send() recv() sendto() shutdown()recvfrom() close() getsockopt() setsockopt() getpeername()getsockname() gethostbyname()gethostbyaddr() getprotobyname()fcntl()TCP连接,等待客户端输入,将内容发送给服务器,并获取客户端地址。
这里,getsocketname()表示获得本地(自己)的地址;getpeername()表示获得连接上的客户端的地址(源IP地址)。<br>server.c#include < sys/types.h> #include < sys/socket.h>#include < netinet/in.h> //sockaddr_in#include < stdio.h>#include < string.h>int main(){ int fd; int clientfd; int ret; pid_t pid; int addrLen = 0; char acbuf[20] = ""; struct sockaddr_in addr = {0}; //自己的地址 struct sockaddr_in clientAddr = {0}; //连上的客户端的地址 //1.socket() fd = socket(PF_INET,SOCK_STREAM,0); if(fd == -1) { perror("socket"); return -1; } //2.bind() addr.sin_family = AF_INET; addr.sin_port = htons(1234); addr.sin_addr.s_addr = inet_addr("192.168.159.6"); ret = bind(fd,(struct sockaddr *)&addr,sizeof(struct sockaddr_in)); if(ret == -1) { perror("bind"); return -1; } //3.listen() ret = listen(fd,10); if(ret == -1) { perror("listen"); return -1; } //4.阻塞 等待 accept() clientfd = accept(fd,NULL,NULL); if(clientfd == -1) { perror("accept"); return -1; }//获取客户端地址addrLen = sizeof(struct sockaddr_in);ret = getpeername(clientfd, (struct sockaddr *)&clientAddr, &addrLen);if(ret == -1){ perror("getpeername"); return -1;}printf("client login.\nip: %s , port: %d\n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));//5.通信while(1){ memset(acbuf,0,20); if (read(clientfd,acbuf,20) > 0) { printf("receive: %s\n",acbuf); }}//6.close()close(fd);return 0;}
client.c
#include#include #include //sockaddr_in#include #include int main(){ int fd; int ret; char acbuf[20] = ""; struct sockaddr_in serAddr = {0}; //1.socket(); fd = socket(PF_INET,SOCK_STREAM,0); if(fd == -1) { perror("socket"); return -1; } //2.连接connect() 服务器的地址 serAddr.sin_family = AF_INET; serAddr.sin_port = htons(1234); serAddr.sin_addr.s_addr = inet_addr("192.168.159.6"); ret = connect(fd,(struct sockaddr *)&serAddr,sizeof(struct sockaddr_in)); if(ret == -1) { perror("connect"); return -1; } //3.通信 while(1) { printf("send: "); fflush(stdout); scanf("%s",acbuf); if(strcmp(acbuf,"exit") == 0) { break; } write(fd,acbuf,strlen(acbuf)); } //4.close() close(fd); return 0;}
运行结果:
做个改进,以上代码,只能一个客户端连接上。因为TCP是基于点对点的,一个accept()对应一个connnect()。要想连接多个客户端,就得使用fork(),一个进程用来专门阻塞等待客户端的连接,一个用来处理与已连接上客户端的通信。代码如下:server.c
int main() { int fd; int clientfd; int ret; pid_t pid; int addrLen = 0; char acbuf[20] = ""; char client_addr[100] = ""; struct sockaddr_in addr = {0}; //自己的地址 struct sockaddr_in clientAddr = {0}; //连上的客户端的地址 signal(SIGCHILD,SIG_IGN); //1.socket() fd = socket(PF_INET,SOCK_STREAM,0); if(fd == -1) { perror("socket"); return -1; } //会出现没有活动的套接字仍然存在,会禁止绑定端口,出现错误:address already in use . //由TCP套接字TIME_WAIT引起,bind 返回 EADDRINUSE,该状态会保留2-4分钟 //if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) //{ // perror("setsockopet error\n"); // return -1; //} //2.bind() addr.sin_family = AF_INET; addr.sin_port = htons(1234); addr.sin_addr.s_addr = inet_addr("192.168.159.6"); ret = bind(fd,(struct sockaddr *)&addr,sizeof(struct sockaddr_in)); if(ret == -1) { perror("bind"); return -1; } //3.listen() ret = listen(fd,10); if(ret == -1) { perror("listen"); return -1; } while(1) { //4.阻塞等待 accept() clientfd = accept(fd,NULL,NULL); if(clientfd == -1) { perror("accept"); return -1; } pid = fork(); //父进程负责继续监听等待,子进程父子与已连接客户端通信 if(pid == -1) { perror("fork"); return -1; } if(pid == 0) //子进程 { //获取客户端地址 addrLen = sizeof(struct sockaddr_in); ret = getpeername(clientfd, (struct sockaddr *)&clientAddr, &addrLen); if(ret == -1) { perror("getpeername"); return -1; } sprintf(client_addr,"ip: %s , port: %d\n",\ inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port)); printf("client longin.\n%s\n",client_addr); //5.通信 while(1) { memset(acbuf,0,20); if (read(clientfd,acbuf,20) == 0) //客户端退出 { //结束相应的server进程 close(clientfd); exit(0); //僵尸进程 } printf("from %sreceive : %s\n\n",client_addr,acbuf); } } else //父进程 { //返回while,继续等待 } } //6.close() close(fd); return 0; }
这里一定要注意,每结束一个客户端,一定要关掉相应的文件描述符,并且结束掉子进程(僵尸进程),不然,随着客户端的增加,进程数会越来越大
client.cint main(){ int fd; int ret; int addrLen; char acbuf[20] = ""; struct sockaddr_in serAddr = {0}; struct sockaddr_in myAddr = {0}; //1.socket(); fd = socket(PF_INET,SOCK_STREAM,0); if(fd == -1) { perror("socket"); return -1; } //2.连接connect() 服务器的地址 serAddr.sin_family = AF_INET; serAddr.sin_port = htons(1234); serAddr.sin_addr.s_addr = inet_addr("192.168.159.6"); ret = connect(fd,(struct sockaddr *)&serAddr,sizeof(struct sockaddr_in)); if(ret == -1) { perror("connect"); return -1; } //获取自己的地址 addrLen = sizeof(struct sockaddr_in); ret = getsockname(fd,(struct sockaddr *)&myAddr,&addrLen); if(ret == -1) { perror("getsockname"); return -1; } printf("client---ip: %s , port: %d\n",\ inet_ntoa(myAddr.sin_addr),ntohs(myAddr.sin_port)); //3.通信 while(1) { printf("send: "); fflush(stdout); scanf("%s",acbuf); if(strcmp(acbuf,"exit") == 0) { break; } write(fd,acbuf,strlen(acbuf)); } //4.close() close(fd); return 0;}
运行结果:
使用UDP连接,完成上述内容。但是发现,使用UDP,因为是面向无连接的,所以在没有收到或者发送包之前,是无法得知源IP地址的。
那UDP如何知道客户端的IP地址和端口呢?1、由客户端显示地高速服务器IP地址和端口,发消息。2、隐式的。服务器从收到的包头中得到源IP和端口号。server.cint main(){ int sockfd; int ret; char acbuf[20] = ""; char client_addr[100] = ""; struct sockaddr_in addr = {0}; struct sockaddr_in clientAddr = {0}; int addrLen = sizeof(struct sockaddr_in); int reuse = 0; //1.socket() sockfd = socket(PF_INET,SOCK_DGRAM,0); if(sockfd == -1) { perror("socket"); return -1; } //2.bind() addr.sin_family = AF_INET; addr.sin_port = htons(1235); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); ret = bind(sockfd,(struct sockaddr *)&addr,addrLen); if(ret == -1) { perror("bind"); return -1; } //3.通信 while(1) { memset(acbuf,0,20); if(recvfrom(sockfd, acbuf, 100,0,(struct sockaddr *)&clientAddr,&addrLen) == -1) { perror("recvfrom"); return -1; } //收到客户端的数据包之后,就可以知道客户端地址 sprintf(client_addr," ip: %s , port: %d\n",\ inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port)); printf("receive from %s: %s\n",client_addr,acbuf); } //4.close close(sockfd); return 0;}
client.c
int main(){ int sockfd; int ret; int addrLen = sizeof(struct sockaddr_in); char acbuf[20] = ""; struct sockaddr_in serAddr = {0}; struct sockaddr_in myAddr = {0}; //1.socket() sockfd = socket(PF_INET,SOCK_DGRAM,0); if(sockfd == -1) { perror("socket"); return -1; } serAddr.sin_family = AF_INET; serAddr.sin_port = htons(1235); serAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //2.通信 while(1) { printf("send: "); fflush(stdout); scanf("%s",acbuf); if(strcmp(acbuf,"exit") == 0) { break; } sendto(sockfd, acbuf, 20,0,(struct sockaddr *)&serAddr,addrLen); //获取自己的地址 ret = getsockname(sockfd,(struct sockaddr *)&myAddr,&addrLen); if(ret == -1) { perror("getsockname"); return -1; } printf("client---ip: %s , port: %d\n\n",\ inet_ntoa(myAddr.sin_addr),ntohs(myAddr.sin_port)); } //3.close close(sockfd); return 0;}
运行结果:
会发现,此时是可以直接运行多个客户端的,因为,UDP是面向无连接的,可以是一对多,多对一,多对多的,只要客户端知道服务器地址,就可以连上。<br>Ps:本人理解有限,还未学习完,有错请指出。转载于:https://blog.51cto.com/13097817/2053955