操作系统里的进程通讯方式有6种:(有名/匿名)管道、信号、消息队列、信号量、内存、套接字。
这是套接字的工作流程
(对于有时间想慢慢看的推荐这篇博客:https://www.cnblogs.com/kefeiGame/p/7246942.html)
我们现在先来实现套接字对同一主机的通讯。(代码注释了函数方法之类的)
服务器(虚拟机[Ubuntu]):
1 #include <unistd.h> 2 #include <string.h> 3 #include <iostream> 4 #include <arpa/inet.h> 5 #include <sys/socket.h> 6 7 #define MYPORT 1223///开应一个端口 8 #define IP "192.168.50.128"///你用的服务器的IPv4地址,这里我用了虚拟机(ubuntu)的地址 9 #define BACKLOG 10 10 #define getLen(zero) sizeof(zero) / sizeof(zero[0]) ///得到数组最大大小 11 using namespace std; 12 13 int main() { 14 int sockfd, new_fd; 15 struct sockaddr_in my_addr; 16 puts("SERVER:"); 17 if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ) { 18 ///socket()函数发生错误则返回-1,否则会返回套接字文件描述符 19 ///对于int socket(int domain, int type, int protocol);中的参数想要详细了解可以看这篇博客:https://blog.csdn.net/liuxingen/article/details/44995467 20 21 perror("socket():");///显示错误 22 return 0; 23 } 24 my_addr.sin_family = AF_INET;///通讯在IPv4网络通信范围内 25 my_addr.sin_port = htons(MYPORT);///我的端口 26 my_addr.sin_addr.s_addr = inet_addr(IP);///用来得到一个32位的IPv4地址,inet_addr将"127.0.0.1"转换成s_addr的无符号整型。 27 bzero(&(my_addr.sin_zero), getLen(my_addr.sin_zero));///sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。 28 29 /** 30 借用以下代码得到了my_addr.sin_addr.s_addr的类型是无符号整型 31 unsigned int a; 32 if(typeid(a) == typeid(my_addr.sin_addr.s_addr)){ 33 puts("Yes"); 34 } 35 **/ 36 37 38 if(bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {///bind()函数将套接字与该IP:端口绑定起来。 39 perror("bind():"); 40 return 0; 41 } 42 if(listen(sockfd, BACKLOG) == -1) {///启动监听,等待接入请求,BACKLOG是在进入队列中允许的连接数目 43 perror("listen():"); 44 return 0; 45 } 46 47 socklen_t sin_size; 48 struct sockaddr_in their_addr; 49 if((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1) { 50 ///当你监听到一个来自客户端的connect请求时,要选择是将他放在请求队列里还是允许其连接,我这里写的其实是单进客户的,所以说无等待。 51 ///这个函数还返回了一个新的套接字,用于与该进程通讯。 52 ///还有一点是之前推荐的c++中的socket编程(入门),该博客里写的sin_size类型是int,可是实际上我在linux的C++环境下出现错误,类型要是socklen_t。 53 perror("accept():"); 54 return 0; 55 } 56 printf("server: got connection from %s\n", inet_ntoa(their_addr.sin_addr));///inet_ntoa可以将inet_addr函数得到的无符号整型转为字符串IP 57 58 char str[1007]; 59 60 while(1) {///循环发送 以endS结束与这一进程的通讯,endS也作为客户端停止工作的标志送出 61 puts("send:"); 62 scanf("%s", str); 63 if(send(new_fd, str, strlen(str), 0) == -1) { 64 ///send()函数,new_fd是accept返回的套接字文件描述符,str就你要发送的数据,数据长度,对于最后一位flag 65 /// flags取值有: 66 /// 0: 与write()无异 67 /// MSG_DONTROUTE:告诉内核,目标主机在本地网络,不用查路由表 68 /// MSG_DONTWAIT:将单个I/O操作设置为非阻塞模式 69 /// MSG_OOB:指明发送的是带外信息 70 71 perror("send():"); 72 close(new_fd);///发送失败就关闭该通讯 73 return 0; 74 } 75 if(!strcmp("endS", str)) 76 break; 77 } 78 close(new_fd);///正常结束要关闭这些已建立的套接字 79 close(sockfd); 80 81 return 0; 82 } 83 84 linux环境的服务端
客户端(虚拟机[Ubuntu]):
1 #include <unistd.h> 2 #include <string.h> 3 #include <iostream> 4 #include <arpa/inet.h> 5 #include <sys/socket.h> 6 7 #define PORT 1223/// 客户机连接远程主机的端口 8 #define MAXDATASIZE 100 /// 每次可以接收的最大字节 9 #define IP "192.168.50.128" 10 #define getLen(zero) sizeof(zero)/sizeof(zero[0]) 11 using namespace std; 12 13 int main( ) { 14 15 int sockfd, numbytes; 16 char buf[MAXDATASIZE];///缓存接收内容 17 struct sockaddr_in their_addr;///和my_addr用法差不多 18 19 puts("USER:"); 20 if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){ 21 perror("socket():"); 22 return 0; 23 } 24 25 their_addr.sin_family = AF_INET; 26 their_addr.sin_port = htons(PORT); 27 28 their_addr.sin_addr.s_addr = inet_addr(IP); 29 bzero(&(their_addr.sin_zero),getLen(their_addr.sin_zero)); 30 if(connect(sockfd,(struct sockaddr *)&their_addr,sizeof(struct sockaddr)) == -1) { 31 ///在客户端这里我们不需要绑定什么东西,因为我们只要向目的IP:端口发起连接请求 32 33 perror("connect():"); 34 return 0; 35 } 36 while(1) {///循环接收 37 if((numbytes=recv(sockfd, buf, MAXDATASIZE, 0)) == -1) {///recv函数,套接字文件描述符,接收到这字符串里,最大长度,flag(之前有解释); 38 perror("recv():"); 39 return 0; 40 } 41 buf[numbytes] = '\0'; 42 if(!strcmp(buf, "endS")) {///接收到endS两边一起结束 43 break; 44 } 45 cout<<"Received: "<<buf<<endl;///输出接收的字符 46 } 47 close(sockfd); 48 return 0; 49 50 }
接下来我们把这个客户端移植到windows操作系统下,代码肯定是要有小改动的。但是这个是最后的操作,我们一步步来:
让虚拟机和本机能够ping通(这个我一开始在网络上找 博客,然后都没用,后面把虚拟机的虚拟网络编辑器恢复默认就可以了,所以说建议自己尝试解决);
因为主机和虚拟机可以用IP地址(IPv4)ping通,也就是可以访问该ip,那么我们的服务器就要在那个客户端(主机)可访问的IP上拿一个端口出来用来通讯。
所以说我们服务器的IP地址要选ifconfig指令里看到的虚拟机里的IPv4地址。
接下来开始移植,其实基本思想和代码结构完全没变。
客户端(windows)
1 #include <iostream> 2 #include <stdlib.h> 3 #include <winsock2.h> 4 #pragma comment(lib,"ws2_32.lib") 5 ///我在codeblocks下不可以运行这些是因为这个libws2_32.a找不到 6 ///解决方法:Settings->compiler->Global compiler settings->(找到)Linker settings(横着排开的目录)->Add->去MinGW/lib找到libws2_32.a就可以了 7 8 9 #define PORT 1223/// 客户机连接远程主机的端口 10 #define MAXDATASIZE 100 /// 每次可以接收的最大字节 11 using namespace std; 12 13 int main( ) { 14 WORD sockVersion = MAKEWORD(2,2); 15 WSADATA wsaData; 16 if(WSAStartup(sockVersion, &wsaData)!=0){ 17 return 0; 18 } 19 ///windows环境下的Winsock是要初始化的;即:固定代码。 20 21 int sockfd, numbytes; 22 char buf[MAXDATASIZE]; 23 struct hostent *he; 24 struct sockaddr_in their_addr; 25 26 puts("USER:"); 27 if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){ 28 cout<<WSAGetLastError()<<endl;///这个可以输出WSAError号 29 perror("socket"); 30 return 0; 31 } 32 33 their_addr.sin_family = AF_INET; 34 their_addr.sin_port = htons(PORT); 35 36 their_addr.sin_addr.s_addr = inet_addr("192.168.50.128"); 37 memset(their_addr.sin_zero, 0, sizeof(their_addr.sin_zero)); 38 if(connect(sockfd,(struct sockaddr *)&their_addr,sizeof(struct sockaddr)) == -1) { 39 perror("connect"); 40 return 0; 41 } 42 while(1) { 43 if((numbytes=recv(sockfd, buf, MAXDATASIZE, 0)) == -1) { 44 perror("recv"); 45 return 0; 46 } 47 buf[numbytes] = '\0'; 48 if(!strcmp(buf, "endS")) { 49 break; 50 } 51 cout<<"Received: "<<buf<<endl; 52 } 53 closesocket(sockfd);///函数不同 54 return 0; 55 }
试着通讯,应该是没问题的!!(至少本地没问题)