macOS跨进程通信: TCP Socket 创建实例
一: 简介
Socket 是 网络传输的抽象概念。
一般我们常用的有
Tcp Socket 能够跨电脑进行通信,即使是在同一个电脑下的多进程间通信,也会通过网卡进行数据传输,如果本地网卡的环回网络被禁用, 则会导致通信失败。Unix Domain Socket ,使用的是Liunx 系统中万物皆文件的概念,和有名管道的操作差不多,都是在文本创建一个特有的文件,用来在两个进程间通信,两个经常分别写入和读取文件流中的数据,达到传输的目的。 和Tcp Socket 不一样的是不用借助网卡通信,限制比较小,传输的效率高。
这里主要针对
在终端使用
可以查看
二:主要函数
1. int socket (int domain, int type, int protocol) 创建socket 对象
domain 选择AF_INET , /* internetwork: UDP, TCP, etc. */type . 选择SOCK_STREAM , 代表Tcp。 如果是udp的话,需要使用SOCK_DGRAM protocol 填0, 由系统选择
2. int bind(int sockfd, const struct sockaddr* myaddr, socklen_t addrlen)
将socket 绑定到对应 ip 和 端口上
sockfd 前面返回的描述符myaddr 包含有ip和port的struct 对象addrlen 前一个stuct的长度
3. int listen(int sockfd, int backlog)
调用后,本地socket端口的状态变更为:
sockfd 前面返回的描述符backlog 此socket 接收的客户端的数量
4. int accept (int sockfd, struct sockaddr *addr, socklen_t *addrlen)
阻塞式等待客户端接入,客户端接入后返回。
传入
后面两个参数代表接口客户端的地址及struct长度
5. int recv(int sockfd, void *buf, int len, unsigned int flags)
tcp的接收客户端发来的数据
6. int write(int sockfd, const void *msg, int len, int flags) 或 int send(int sockfd, void *buf, int len, unsigned int flags)
tcp 往 客户端/服务器发送数据
7. int close(int sockfd) 或 Windows的 7. int closesocket(int sockfd)
关闭连接
三:demo代码
如下图,创建了两个进程,分别为服务器(app), 客户端为 命令行程序(客户端的代码可以直接移植到win)
1. 服务器端主要逻辑
- 主要创建了
socket 一个internetwork (AF_INET )和TCP (SOCK_STREAM )组合的socket - 绑定任何ip的
8766 端口(代表任意ip的客户端都可以连接过来) listen() 开始监听- 启动子线程,在线程内 阻塞等待客户端连接(
accept ),和接收客户端消息(read ) - 启动客户端命令行进程。(这里可以在本地其他地方或者局域网内的其他电脑启动命令行)
- 点击ui上的发送按钮,往客户端发送消息
主要代码:
#import "ViewController.h" #import <sys/socket.h> #include <thread> @interface ViewController () @property (weak) IBOutlet NSTextField *textLabel; @property (nonatomic, assign) int listenFd; @property (nonatomic, assign) int currListenFd; @end static void subThreadWorker(ViewController *vc); @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; int listenFd; listenFd = socket(AF_INET, SOCK_STREAM, 0); self.listenFd = listenFd; if (listenFd == -1) { perror("socket create failed!"); return; } // INADDR_ANY是ANY,是绑定地址0.0.0.0上的监听, 能收到任意一块网卡的连接; // INADDR_LOOPBACK, 也就是绑定地址LOOPBAC, 往往是127.0.0.1, 只能收到127.0.0.1上面的连接请求 /** serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); $ netstat -nta -p tcp | grep LISTEN tcp4 0 0 *.8765 *.* LISTEN ------ serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //tcp4 0 0 127.0.0.1.8766 *.* LISTEN */ struct sockaddr_in serverAddr = {0}; serverAddr.sin_family = AF_INET; // serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); serverAddr.sin_addr.s_addr = INADDR_ANY; //接受任意ip // serverAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); serverAddr.sin_port = htons(8766); //监听端口号8766 int ret = bind(listenFd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)); if (ret == -1) { perror("socket bind failed!"); return; } //5代表正在等待完成相应的TCP三次握手过程的队列长度 ret = listen(listenFd, 5); if (ret == -1) { printf("socket listen failed! error: %s(errno: %d) ",strerror(errno),errno); perror("socket listen failed!"); return; } std::thread(subThreadWorker, self).detach(); printf("创建成功! "); //启动子进程 NSURL *subAppURL = [NSURL fileURLWithPath: [NSString stringWithFormat:@"%@/Socket_TCP_ClientApp", [NSBundle mainBundle].executablePath.stringByDeletingLastPathComponent]]; [[NSWorkspace sharedWorkspace] openURL:subAppURL configuration: [NSWorkspaceOpenConfiguration configuration] completionHandler:nil]; } static void subThreadWorker(ViewController *vc) { //单独线程监听服务器发回来的消息 ssize_t numRed = 0; static const int buffer_size = 4096; char buff[buffer_size]; int tmpListenFd = vc.listenFd; while (tmpListenFd != -1) { printf("服务器等待客户端 %i 连接... ", vc.listenFd); tmpListenFd = accept(tmpListenFd, NULL, NULL); printf("收到客户端连接。 sfd:%i ", vc.listenFd); if (vc.listenFd == -1) { printf("socket accept failed! error: %s(errno: %d) ",strerror(errno),errno); perror("这是一个无效的连接!"); break; } vc.currListenFd = tmpListenFd; //循环读取 client 发来的消息 while ((numRed = read(tmpListenFd, buff, buffer_size)) > 0) { printf("服务器收到客户端发的数据: %s ", buff); } if (numRed == -1) { perror("numRed == -1!"); break; } if (close(tmpListenFd) == -1) { perror("close faild!"); break; } tmpListenFd = vc.listenFd; printf("for over! "); } } //点击按钮 - (IBAction)sendMsgToClient:(id)sender { const char *backBuffer = [self.textLabel.stringValue UTF8String]; ssize_t sendLen = write(self.currListenFd, backBuffer, strlen(backBuffer)+1); if (sendLen < 0) { printf("error:%i ", errno); perror("服务器发送给客户端失败!reason:"); } else { printf("服务器发送给客户端成功!len:%zi ", sendLen); } } - (void)dealloc { } @end
2. 客户端主要逻辑
- 如果是windows,需要首先调用
WSAStartup(...) - 同样创建socket 连接,tcp 类型的。
socket(AF_INET, SOCK_STREAM, 0); - 连接到服务器,地址为:
"10.34.133.46:8766" (如果本机的话可以使用127.0.0.1),connect(...) - 向服务器发送一条消息,
send(client_fd, buf, sizeof(buf), 0) - 收到一次消息,就退出程序.
recv(client_fd, buf, sizeof(buf), 0) - 处理退出前的清理工作。
主要代码:
// // main.cpp // Socket_TCP_ClientApp // // Created by jimbo on 2024/1/22. // #include <iostream> #include <stdio.h> #include <stdlib.h> #include <memory.h> #ifdef _WIN32 #include <system_error> #include <WS2tcpip.h> #pragma warning(disable:4996) #else #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <arpa/inet.h> #endif // _WIN32 #pragma comment(lib, "ws2_32.lib") #ifdef _WIN32 class SocketInit { public: SocketInit() { //Win 必须在socket 使用前调用 WSAStartup() WSADATA data; int ret = WSAStartup(MAKEWORD(2, 1), &data); printf("WSAStartup val:%d ", ret); } ~SocketInit() { printf("~SocketInit "); WSACleanup(); } }; const SocketInit sInit; #endif // _WIN32 int main(int argc, char* argv[]) { printf(" 我是Client "); int client_fd; struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(8766); //端口 server_addr.sin_addr.s_addr = inet_addr("10.34.133.46"); //ip //创建连接 client_fd = socket(AF_INET, SOCK_STREAM, 0); if (client_fd < 0) { #ifdef _WIN32 char buf[1024]; strerror_s(buf, 1024, errno); #else char *buf = strerror(errno); #endif // _WIN32 printf("socket create failed. %s(errno:%d) ", buf, errno); exit(EXIT_FAILURE); } //连接到服务器 if (connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { perror("socket connect failed"); exit(EXIT_FAILURE); } printf("socket connect success! "); char buf[1024] = "hello socket from client!"; //向服务器发送一条消息 if (send(client_fd, buf, sizeof(buf), 0) < 0) { perror("socket send failed"); exit(EXIT_FAILURE); } printf("socket send success! "); //收到一次消息,就退出程序 if (recv(client_fd, buf, sizeof(buf), 0) < 0) { perror("socket recv failed"); exit(EXIT_FAILURE); } printf("client recv response: [%s] ", buf); #ifdef _WIN32 /* 关闭socket */ closesocket(client_fd); #else /* 关闭socket */ close(client_fd); #endif // _WIN32 printf("client ended successfully "); exit(EXIT_SUCCESS); }