macOS跨进程通信: TCP Socket 创建实例
一: 简介
Socket 是 网络传输的抽象概念。
一般我们常用的有Tcp Socket
和 UDP Scoket
, 和类Unix 系统(包括Mac)独有的 Unix Domain Socket
(UDS)。
Tcp Socket
能够跨电脑进行通信,即使是在同一个电脑下的多进程间通信,也会通过网卡进行数据传输,如果本地网卡的环回网络被禁用, 则会导致通信失败。Unix Domain Socket
,使用的是Liunx 系统中万物皆文件的概念,和有名管道的操作差不多,都是在文本创建一个特有的文件,用来在两个进程间通信,两个经常分别写入和读取文件流中的数据,达到传输的目的。 和Tcp Socket
不一样的是不用借助网卡通信,限制比较小,传输的效率高。
这里主要针对Tcp Socket
进行研究.
在终端使用 netstat -nta -p tcp | grep 8766
可以查看 8766
端口的连接情况,可以看到 已经有 62106
端口的客户端连接成功。
二:主要函数
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端口的状态变更为:LISTEN
, 可以使用netstat -nta -p tcp | grep 8766
在终端查看
sockfd
前面返回的描述符backlog
此socket 接收的客户端的数量
4. int accept (int sockfd, struct sockaddr *addr, socklen_t *addrlen)
阻塞式等待客户端接入,客户端接入后返回。
传入server
的 sockfd
,返回接入后的sockfd
.
后面两个参数代表接口客户端的地址及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上的发送按钮,往客户端发送消息
主要代码: ViewController.mm
文件代码
#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)\n",strerror(errno),errno);
perror("socket listen failed!");
return;
}
std::thread(subThreadWorker, self).detach();
printf("创建成功!\n");
//启动子进程
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 连接...\n", vc.listenFd);
tmpListenFd = accept(tmpListenFd, NULL, NULL);
printf("收到客户端连接。 sfd:%i\n", vc.listenFd);
if (vc.listenFd == -1) {
printf("socket accept failed! error: %s(errno: %d)\n",strerror(errno),errno);
perror("这是一个无效的连接!");
break;
}
vc.currListenFd = tmpListenFd;
//循环读取 client 发来的消息
while ((numRed = read(tmpListenFd, buff, buffer_size)) > 0) {
printf("服务器收到客户端发的数据: %s\n", buff);
}
if (numRed == -1) {
perror("numRed == -1!");
break;
}
if (close(tmpListenFd) == -1) {
perror("close faild!");
break;
}
tmpListenFd = vc.listenFd;
printf("for over!\n");
}
}
//点击按钮
- (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\n", errno);
perror("服务器发送给客户端失败!reason:");
} else {
printf("服务器发送给客户端成功!len:%zi\n", 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
文件代码
//
// 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\n", ret);
}
~SocketInit() {
printf("~SocketInit\n");
WSACleanup();
}
};
const SocketInit sInit;
#endif // _WIN32
int main(int argc, char* argv[]) {
printf("\n\n我是Client\n\n");
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)\n", 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!\n");
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!\n");
//收到一次消息,就退出程序
if (recv(client_fd, buf, sizeof(buf), 0) < 0) {
perror("socket recv failed");
exit(EXIT_FAILURE);
}
printf("client recv response: [%s]\n", buf);
#ifdef _WIN32
/* 关闭socket */
closesocket(client_fd);
#else
/* 关闭socket */
close(client_fd);
#endif // _WIN32
printf("client ended successfully\n");
exit(EXIT_SUCCESS);
}