MACIOS Socket编程
转自 https://github.com/kejinlu/objc-doc/blob/master/Socket%E7%BC%96%E7%A8%8B.md
大纲
- 一.Socket简介
- 二.BSD Socket编程准备
- 1.地址
- 2.端口
- 3.网络字节序
- 4.半相关与全相关
- 5.网络编程模型
- 三.socket接口编程示例
- 四.使用select
- 五.使用kqueue
- 六.使用流
注:文档中设计涉及的代码也都在本人github目录下,分别为socketServer和socketClient.对应着各个分支。
一.Socket简介
#define STDIN_FILENO 0 /* standard input file descriptor */
#define STDOUT_FILENO 1 /* standard output file descriptor */
#define STDERR_FILENO 2 /* standard error file descriptor */
在Mac系统中,可以通过Activity Monitor来查看某个进程打开的文件和端口。
二.BSD Socket编程准备
1.地址
在程序中,我们如何保存一个地址呢?在 <sys/socket.h>
中的sockaddr便是描述socket地址的结构体类型.
/*
* [XSI] Structure used by kernel to store most addresses.
*/
struct sockaddr {__uint8_t sa_len; /* total length */sa_family_t sa_family; /* [XSI] address family */char sa_data[14]; /* [XSI] addr value (actually larger) */
};
为了方便设置用语网络通信的socket地址,引入了sockaddr_in结构体(对于UNIX Domain Socket则对应sockaddr_un)
/** Socket address, internet style.*/
struct sockaddr_in {__uint8_t sin_len;sa_family_t sin_family;in_port_t sin_port;//得是网络字节序struct in_addr sin_addr;//in_addr存在的原因则是历史原因,其实质是代表一个IP地址的32位整数char sin_zero[8];//bzero之,纯粹是为了兼容sockaddr
};
在实际编程的时候,经常需要将sockaddr_in强制转换成sockaddr类型。
2.端口
说到端口我们经常会联想到硬件,在网络编程中的端口其实是一个标识而已,或者说是系统的资源而已。系统提供了端口分配和管理的机制。
3.网络字节序
低位 | > > | > > | 高位 |
---|---|---|---|
12 | 34 | 56 | 78 |
低位 | > > | > > | 高位 |
---|---|---|---|
78 | 56 | 34 | 12 |
TCP/IP 各层协议将字节序使用的是大端序,我们把TCP/IP协议中使用的字节序称之为网络字节序。 编程的时候可以使用定义在sys/_endian.h
中的相关的接口进行本地字节序和网络字节序的互转。
#define ntohs(x) __DARWIN_OSSwapInt16(x) // 16位整数 网络字节序转主机字节序
#define htons(x) __DARWIN_OSSwapInt16(x) // 16位整数 主机字节序转网络字节序#define ntohl(x) __DARWIN_OSSwapInt32(x) //32位整数 网络字节序转主机字节序
#define htonl(x) __DARWIN_OSSwapInt32(x) //32位整数 主机字节序转网络字节序
以上声明中 n代表netwrok, h代表host ,s代表short,l代表long
如果数据是单字节的话,则其没有字节序的说法了。
4.半相关与全相关
5.网络编程模型
服务器端 | 客户端 |
---|---|
创建Socket | - |
将Socket和本地的地址端口绑定 | - |
开始进行侦听 | 创建一个Socket和服务器的地址并通过它们向服务器发送连接请求 |
握手成功,接受请求,得到一个新的Socket,通过它可以和客户端进行通信 | 连接成功,客户端的Socket会绑定到系统分配的一个端口上,并可以通过它和服务器端进行通信 |
三.BSD Socket编程详解
下面的例子是一个简单的一对一聊天的程序,分服务器和客户端,且发送消息和接受消息次序固定。
Server端代码
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>int main (int argc, const char * argv[])
{struct sockaddr_in server_addr;server_addr.sin_len = sizeof(struct sockaddr_in);server_addr.sin_family = AF_INET;//Address families AF_INET互联网地址簇server_addr.sin_port = htons(11332);server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");bzero(&(server_addr.sin_zero),8);//创建socketint server_socket = socket(AF_INET, SOCK_STREAM, 0);//SOCK_STREAM 有连接if (server_socket == -1) {perror("socket error");return 1;}//绑定socket:将创建的socket绑定到本地的IP地址和端口,此socket是半相关的,只是负责侦听客户端的连接请求,并不能用于和客户端通信int bind_result = bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));if (bind_result == -1) {perror("bind error");return 1;}//listen侦听 第一个参数是套接字,第二个参数为等待接受的连接的队列的大小,在connect请求过来的时候,完成三次握手后先将连接放到这个队列中,直到被accept处理。如果这个队列满了,且有新的连接的时候,对方可能会收到出错信息。if (listen(server_socket, 5) == -1) {perror("listen error");return 1;}struct sockaddr_in client_address;socklen_t address_len;int client_socket = accept(server_socket, (struct sockaddr *)&client_address, &address_len);//返回的client_socket为一个全相关的socket,其中包含client的地址和端口信息,通过client_socket可以和客户端进行通信。if (client_socket == -1) {perror("accept error");return -1;}char recv_msg[1024];char reply_msg[1024];while (1) {bzero(recv_msg, 1024);bzero(reply_msg, 1024);printf("reply:");scanf("%s",reply_msg);send(client_socket, reply_msg, 1024, 0);long byte_num = recv(client_socket,recv_msg,1024,0);recv_msg[byte_num] = '\0';printf("client said:%s\n",recv_msg);}return 0;
}
Client端代码
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>int main (int argc, const char * argv[])
{struct sockaddr_in server_addr;server_addr.sin_len = sizeof(struct sockaddr_in);server_addr.sin_family = AF_INET;server_addr.sin_port = htons(11332);server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");bzero(&(server_addr.sin_zero),8);int server_socket = socket(AF_INET, SOCK_STREAM, 0);if (server_socket == -1) {perror("socket error");return 1;}char recv_msg[1024];char reply_msg[1024];if (connect(server_socket, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in))==0) {//connect 成功之后,其实系统将你创建的socket绑定到一个系统分配的端口上,且其为全相关,包含服务器端的信息,可以用来和服务器端进行通信。while (1) {bzero(recv_msg, 1024);bzero(reply_msg, 1024);long byte_num = recv(server_socket,recv_msg,1024,0);recv_msg[byte_num] = '\0';printf("server said:%s\n",recv_msg);printf("reply:");scanf("%s",reply_msg);if (send(server_socket, reply_msg, 1024, 0) == -1) {perror("send error");}}}// insert code here...printf("Hello, World!\n");return 0;
}
上面的服务器端和客户端连接成功之后打开的端口的情况是怎么样的呢?
服务器端 ,存在一个用于listen的半相关的socket,一个用于和客户端进行通信的全相关的socket
客户端 存在一个用于和服务器端进行通信的全相关的socket
由 于accept只运行了一次,所以服务器端一次只能和一个客户端进行通信,且使用的send和recv方法都是阻塞的,所以上面这个例子存在一个问题就是 服务器端客户端连接成功之后,发送,接受,发送,接受的次序就被固定了。比如服务器端发送消息之后就等客户端发送消息了,没有接受到客户端的消息之前服务 器端是没有办法发送消息的。使用select这个这个系统调用可以解决上面的问题。
四.使用select
- 1.创建一个fd_set变量(fd_set实为包含了一个整数数组的结构体),用来存放所有的待检查的文件描述符
- 2.清空fd_set变量,并将需要检查的所有文件描述符加入fd_set
- 3.调用select。若返回-1,则说明出错;返回0,则说明超时,返回正数,则为发生状态变化的文件描述符的个数
- 4.若select返回大于0,则依次查看哪些文件描述符变的可读,并对它们进行处理
- 5.返回步骤2,开始新一轮的检测
服务器端
#include <stdio.h>#include <stdlib.h>#include <netinet/in.h>#include <sys/socket.h>#include <arpa/inet.h>#include <string.h>#include <unistd.h>#define BACKLOG 5 //完成三次握手但没有accept的队列的长度#define CONCURRENT_MAX 8 //应用层同时可以处理的连接#define SERVER_PORT 11332#define BUFFER_SIZE 1024#define QUIT_CMD ".quit"int client_fds[CONCURRENT_MAX];int main (int argc, const char * argv[]){char input_msg[BUFFER_SIZE];char recv_msg[BUFFER_SIZE]; //本地地址struct sockaddr_in server_addr;server_addr.sin_len = sizeof(struct sockaddr_in);server_addr.sin_family = AF_INET;server_addr.sin_port = htons(SERVER_PORT);server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");bzero(&(server_addr.sin_zero),8);//创建socketint server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);if (server_sock_fd == -1) {perror("socket error");return 1;}//绑定socketint bind_result = bind(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));if (bind_result == -1) {perror("bind error");return 1;}//listenif (listen(server_sock_fd, BACKLOG) == -1) {perror("listen error");return 1;}//fd_setfd_set server_fd_set;int max_fd = -1;struct timeval tv;tv.tv_sec = 20;tv.tv_usec = 0;while (1) {FD_ZERO(&server_fd_set);//标准输入FD_SET(STDIN_FILENO, &server_fd_set);if (max_fd < STDIN_FILENO) {max_fd = STDIN_FILENO;}//服务器端socketFD_SET(server_sock_fd, &server_fd_set);if (max_fd < server_sock_fd) {max_fd = server_sock_fd;}//客户端连接for (int i = 0; i < CONCURRENT_MAX; i++) {if (client_fds[i]!=0) {FD_SET(client_fds[i], &server_fd_set);if (max_fd < client_fds[i]) {max_fd = client_fds[i];}}}int ret = select(max_fd+1, &server_fd_set, NULL, NULL, &tv);if (ret < 0) {perror("select 出错\n");continue;}else if(ret == 0){printf("select 超时\n");continue;}else{//ret为未状态发生变化的文件描述符的个数if (FD_ISSET(STDIN_FILENO, &server_fd_set)) {//标准输入bzero(input_msg, BUFFER_SIZE);fgets(input_msg, BUFFER_SIZE, stdin);//输入 ".quit" 则退出服务器if (strcmp(input_msg, QUIT_CMD) == 0) {exit(0);}for (int i=0; i<CONCURRENT_MAX; i++) {if (client_fds[i]!=0) {send(client_fds[i], input_msg, BUFFER_SIZE, 0);}}}if (FD_ISSET(server_sock_fd, &server_fd_set)) {//有新的连接请求struct sockaddr_in client_address;socklen_t address_len;int client_socket_fd = accept(server_sock_fd, (struct sockaddr *)&client_address, &address_len);if (client_socket_fd > 0) {int index = -1;for (int i = 0; i < CONCURRENT_MAX; i++) {if (client_fds[i] == 0) {index = i;client_fds[i] = client_socket_fd;break;}}if (index >= 0) {printf("新客户端(%d)加入成功 %s:%d \n",index,inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));}else{bzero(input_msg, BUFFER_SIZE);strcpy(input_msg, "服务器加入的客户端数达到最大值,无法加入!\n");send(client_socket_fd, input_msg, BUFFER_SIZE, 0);printf("客户端连接数达到最大值,新客户端加入失败 %s:%d \n",inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));}}}for (int i = 0; i <CONCURRENT_MAX; i++) {if (client_fds[i]!=0) {if (FD_ISSET(client_fds[i], &server_fd_set)) {//处理某个客户端过来的消息bzero(recv_msg, BUFFER_SIZE);long byte_num = recv(client_fds[i],recv_msg,BUFFER_SIZE,0);if (byte_num > 0) {if (byte_num > BUFFER_SIZE) {byte_num = BUFFER_SIZE;}recv_msg[byte_num] = '\0';printf("客户端(%d):%s\n",i,recv_msg);}else if(byte_num < 0){printf("从客户端(%d)接受消息出错.\n",i);}else{FD_CLR(client_fds[i], &server_fd_set);client_fds[i] = 0;printf("客户端(%d)退出了\n",i);}}}}}}return 0;}
客户端
#include <stdio.h>#include <netinet/in.h>#include <sys/socket.h>#include <arpa/inet.h>#include <string.h>#include <unistd.h>#include <stdlib.h>#define BUFFER_SIZE 1024int main (int argc, const char * argv[]){struct sockaddr_in server_addr;server_addr.sin_len = sizeof(struct sockaddr_in);server_addr.sin_family = AF_INET;server_addr.sin_port = htons(11332);server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");bzero(&(server_addr.sin_zero),8);int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);if (server_sock_fd == -1) {perror("socket error");return 1;}char recv_msg[BUFFER_SIZE];char input_msg[BUFFER_SIZE];if (connect(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in))==0) {fd_set client_fd_set;struct timeval tv;tv.tv_sec = 20;tv.tv_usec = 0;while (1) {FD_ZERO(&client_fd_set);FD_SET(STDIN_FILENO, &client_fd_set);FD_SET(server_sock_fd, &client_fd_set);int ret = select(server_sock_fd + 1, &client_fd_set, NULL, NULL, &tv);if (ret < 0 ) {printf("select 出错!\n");continue;}else if(ret ==0){printf("select 超时!\n");continue;}else{if (FD_ISSET(STDIN_FILENO, &client_fd_set)) {bzero(input_msg, BUFFER_SIZE);fgets(input_msg, BUFFER_SIZE, stdin);if (send(server_sock_fd, input_msg, BUFFER_SIZE, 0) == -1) {perror("发送消息出错!\n");}}if (FD_ISSET(server_sock_fd, &client_fd_set)) {bzero(recv_msg, BUFFER_SIZE);long byte_num = recv(server_sock_fd,recv_msg,BUFFER_SIZE,0);if (byte_num > 0) {if (byte_num > BUFFER_SIZE) {byte_num = BUFFER_SIZE;}recv_msg[byte_num] = '\0';printf("服务器:%s\n",recv_msg);}else if(byte_num < 0){printf("接受消息出错!\n");}else{printf("服务器端退出!\n");exit(0);}}}}}return 0;}
//sys/_structs.h
#define __DARWIN_FD_SETSIZE 1024
/
//Kernel.framework sys/select.h
#define FD_SETSIZE __DARWIN_FD_SETSIZE
五.使用kqueue
kqueue中涉及两个系统调用,kqueue()和kevent()
- 创建kqueue
- 创建struct kevent变量(注意这里的kevent是结构体类型名),可以通过EV_SET这个宏提供的快捷方式进行创建
- 通过kevent系统调用将创建好的kevent结构体变量加入到kqueue队列中,完成对指定文件描述符的事件的订阅
通过kevent系统调用获取满足条件的事件队列,并对每一个事件进行处理
#include <stdio.h> #include <stdlib.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/event.h> #include <sys/types.h> #include <sys/time.h> #include <arpa/inet.h> #include <string.h> #include <unistd.h> #define BACKLOG 5 //完成三次握手但没有accept的队列的长度 #define CONCURRENT_MAX 8 //应用层同时可以处理的连接 #define SERVER_PORT 11332 #define BUFFER_SIZE 1024 #define QUIT_CMD ".quit" int client_fds[CONCURRENT_MAX]; struct kevent events[10];//CONCURRENT_MAX + 2 int main (int argc, const char * argv[]) {char input_msg[BUFFER_SIZE];char recv_msg[BUFFER_SIZE];//本地地址struct sockaddr_in server_addr;server_addr.sin_len = sizeof(struct sockaddr_in);server_addr.sin_family = AF_INET;server_addr.sin_port = htons(SERVER_PORT);server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");bzero(&(server_addr.sin_zero),8);//创建socketint server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);if (server_sock_fd == -1) {perror("socket error");return 1;}//绑定socketint bind_result = bind(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));if (bind_result == -1) {perror("bind error");return 1;}//listenif (listen(server_sock_fd, BACKLOG) == -1) {perror("listen error");return 1;}struct timespec timeout = {10,0};//kqueueint kq = kqueue();if (kq == -1) {perror("创建kqueue出错!\n");exit(1);}struct kevent event_change;EV_SET(&event_change, STDIN_FILENO, EVFILT_READ, EV_ADD, 0, 0, NULL);kevent(kq, &event_change, 1, NULL, 0, NULL);EV_SET(&event_change, server_sock_fd, EVFILT_READ, EV_ADD, 0, 0, NULL);kevent(kq, &event_change, 1, NULL, 0, NULL);while (1) {int ret = kevent(kq, NULL, 0, events, 10, &timeout);if (ret < 0) {printf("kevent 出错!\n");continue;}else if(ret == 0){printf("kenvent 超时!\n");continue;}else{//ret > 0 返回事件放在events中for (int i = 0; i < ret; i++) {struct kevent current_event = events[i];//kevent中的ident就是文件描述符if (current_event.ident == STDIN_FILENO) {//标准输入bzero(input_msg, BUFFER_SIZE);fgets(input_msg, BUFFER_SIZE, stdin);//输入 ".quit" 则退出服务器if (strcmp(input_msg, QUIT_CMD) == 0) {exit(0);}for (int i=0; i<CONCURRENT_MAX; i++) {if (client_fds[i]!=0) {send(client_fds[i], input_msg, BUFFER_SIZE, 0);}}}else if(current_event.ident == server_sock_fd){//有新的连接请求struct sockaddr_in client_address;socklen_t address_len;int client_socket_fd = accept(server_sock_fd, (struct sockaddr *)&client_address, &address_len);if (client_socket_fd > 0) {int index = -1;for (int i = 0; i < CONCURRENT_MAX; i++) {if (client_fds[i] == 0) {index = i;client_fds[i] = client_socket_fd;break;}}if (index >= 0) {EV_SET(&event_change, client_socket_fd, EVFILT_READ, EV_ADD, 0, 0, NULL);kevent(kq, &event_change, 1, NULL, 0, NULL);printf("新客户端(fd = %d)加入成功 %s:%d \n",client_socket_fd,inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));}else{bzero(input_msg, BUFFER_SIZE);strcpy(input_msg, "服务器加入的客户端数达到最大值,无法加入!\n");send(client_socket_fd, input_msg, BUFFER_SIZE, 0);printf("客户端连接数达到最大值,新客户端加入失败 %s:%d \n",inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));}}}else{//处理某个客户端过来的消息bzero(recv_msg, BUFFER_SIZE);long byte_num = recv((int)current_event.ident,recv_msg,BUFFER_SIZE,0);if (byte_num > 0) {if (byte_num > BUFFER_SIZE) {byte_num = BUFFER_SIZE;}recv_msg[byte_num] = '\0';printf("客户端(fd = %d):%s\n",(int)current_event.ident,recv_msg);}else if(byte_num < 0){printf("从客户端(fd = %d)接受消息出错.\n",(int)current_event.ident);}else{EV_SET(&event_change, current_event.ident, EVFILT_READ, EV_DELETE, 0, 0, NULL);kevent(kq, &event_change, 1, NULL, 0, NULL);close((int)current_event.ident);for (int i = 0; i < CONCURRENT_MAX; i++) {if (client_fds[i] == (int)current_event.ident) {client_fds[i] = 0;break;}}printf("客户端(fd = %d)退出了\n",(int)current_event.ident);}}}}}return 0; }
其实kqueue的应用场景非常的广阔,可以监控文件系统中文件的变化(对文件变化的事件可以粒度非常的细,具体可以查看kqueue的手册),监控系统进程的生命周期。GCD的事件处理便是建立在kqueue之上的。
六.使用Streams
- (BOOL)run:(NSError **)error{BOOL successful = YES;CFSocketContext socketCtxt = {0, self, NULL, NULL, NULL};_socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack,(CFSocketCallBack)&SocketConnectionAcceptedCallBack,&socketCtxt);if (NULL == _socket) {if (nil != error) {*error = [[NSError alloc] initWithDomain:ServerErrorDomaincode:kServerNoSocketsAvailableuserInfo:nil];}successful = NO;}if(YES == successful) {// enable address reuseint yes = 1;setsockopt(CFSocketGetNative(_socket), SOL_SOCKET, SO_REUSEADDR,(void *)&yes, sizeof(yes));uint8_t packetSize = 128;setsockopt(CFSocketGetNative(_socket),SOL_SOCKET, SO_SNDBUF,(void *)&packetSize, sizeof(packetSize));setsockopt(CFSocketGetNative(_socket),SOL_SOCKET, SO_RCVBUF,(void *)&packetSize, sizeof(packetSize));struct sockaddr_in addr4;memset(&addr4, 0, sizeof(addr4));addr4.sin_len = sizeof(addr4);addr4.sin_family = AF_INET;addr4.sin_port = htons(CHAT_SERVER_PORT); addr4.sin_addr.s_addr = htonl(INADDR_ANY);NSData *address4 = [NSData dataWithBytes:&addr4 length:sizeof(addr4)];if (kCFSocketSuccess != CFSocketSetAddress(_socket, (CFDataRef)address4)) {if (error) *error = [[NSError alloc] initWithDomain:ServerErrorDomaincode:kServerCouldNotBindToIPv4AddressuserInfo:nil];if (_socket) CFRelease(_socket);_socket = NULL;successful = NO;} else {// now that the binding was successful, we get the port number NSData *addr = [(NSData *)CFSocketCopyAddress(_socket) autorelease];memcpy(&addr4, [addr bytes], [addr length]);self.port = ntohs(addr4.sin_port);// 将socket 输入源加入到当前的runloopCFRunLoopRef cfrl = CFRunLoopGetCurrent();CFRunLoopSourceRef source4 = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socket, 0);CFRunLoopAddSource(cfrl, source4, kCFRunLoopDefaultMode);CFRelease(source4); //标准输入,当在命令行中输入时,回调函数便会被调用CFFileDescriptorContext context = {0,self,NULL,NULL,NULL};CFFileDescriptorRef stdinFDRef = CFFileDescriptorCreate(kCFAllocatorDefault, STDIN_FILENO, true, FileDescriptorCallBack, &context);CFFileDescriptorEnableCallBacks(stdinFDRef,kCFFileDescriptorReadCallBack);CFRunLoopSourceRef stdinSource = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, stdinFDRef, 0);CFRunLoopAddSource(cfrl, stdinSource, kCFRunLoopDefaultMode);CFRelease(stdinSource);CFRelease(stdinFDRef); CFRunLoopRun();}}return successful;
}
SocketConnectionAcceptedCallBack函数
static void SocketConnectionAcceptedCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) {ChatServer *theChatServer = (ChatServer *)info;if (kCFSocketAcceptCallBack == type) { // 摘自kCFSocketAcceptCallBack的文档,New connections will be automatically accepted and the callback is called with the data argument being a pointer to a CFSocketNativeHandle of the child socket. This callback is usable only with listening sockets.CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;// create the read and write streams for the connection to the other processCFReadStreamRef readStream = NULL;CFWriteStreamRef writeStream = NULL;CFStreamCreatePairWithSocket(kCFAllocatorDefault, nativeSocketHandle,&readStream, &writeStream);if(NULL != readStream && NULL != writeStream) {CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket,kCFBooleanTrue);CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket,kCFBooleanTrue);NSInputStream *inputStream = (NSInputStream *)readStream;//toll-free bridgingNSOutputStream *outputStream = (NSOutputStream *)writeStream;//toll-free bridginginputStream.delegate = theChatServer;[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];[inputStream open];outputStream.delegate = theChatServer;[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];[outputStream open];Client *aClient = [[Client alloc] init];aClient.inputStream = inputStream;aClient.outputStream = outputStream;aClient.sock_fd = nativeSocketHandle;[theChatServer.clients setValue:aClient forKey:[NSString stringWithFormat:@"%d",inputStream]];NSLog(@"有新客户端(sock_fd=%d)加入",nativeSocketHandle);} else {close(nativeSocketHandle);}if (readStream) CFRelease(readStream);if (writeStream) CFRelease(writeStream);}
}
当客户端有数据传过来时,相应的NSInputStream的delegate方法被调用
- (void) stream:(NSStream*)stream handleEvent:(NSStreamEvent)eventCode {switch (eventCode) {case NSStreamEventOpenCompleted: {break;}case NSStreamEventHasBytesAvailable: {Client *client = [self.clients objectForKey:[NSString stringWithFormat:@"%d",stream]];NSMutableData *data = [NSMutableData data];uint8_t *buf = calloc(128, sizeof(uint8_t));NSUInteger len = 0;while([(NSInputStream*)stream hasBytesAvailable]) {len = [(NSInputStream*)stream read:buf maxLength:128];if(len > 0) {[data appendBytes:buf length:len];}}free(buf);if ([data length] == 0) {//客户端退出NSLog(@"客户端(sock_fd=%d)退出",client.sock_fd);[self.clients removeObjectForKey:[NSString stringWithFormat:@"%d",stream]];close(client.sock_fd);}else{NSLog(@"收到客户端(sock_fd=%d)消息:%@",client.sock_fd,[[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]);}break;}case NSStreamEventHasSpaceAvailable: {break;}case NSStreamEventEndEncountered: {break;}case NSStreamEventErrorOccurred: {break;}default:break;}
}
当在debug窗口中输入内容并回车时,标准输入缓冲区中便有数据了,这个时候回调函数FileDescriptorCallBack将被调用,处理标准输入。
static void FileDescriptorCallBack(CFFileDescriptorRef f,CFOptionFlags callBackTypes,void *info){int fd = CFFileDescriptorGetNativeDescriptor(f);ChatServer *theChatServer = (ChatServer *)info;if (fd == STDIN_FILENO) {NSData *inputData = [[NSFileHandle fileHandleWithStandardInput] availableData];NSString *inputString = [[[NSString alloc] initWithData:inputData encoding:NSUTF8StringEncoding] autorelease];NSLog(@"准备发送消息:%@",inputString);for (Client *client in [theChatServer.clients allValues]) {[client.outputStream write:[inputData bytes] maxLength:[inputData length]];}//处理完数据之后必须重新Enable 回调函数CFFileDescriptorEnableCallBacks(f,kCFFileDescriptorReadCallBack);}
}
转载于:https://www.cnblogs.com/sinuo-come-on/p/1-1.html
MACIOS Socket编程相关推荐
- Windows Socket编程笔记之最简单的小Demo
Windows Socket编程的大致过程: 服务器端: ----过程-------------对应的API------- 0.初始化 | WSAStartup() 1.创建So ...
- Linux下Socket编程
Linux下Socket编程 网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符.Socket也具有一个类似于打开文件的函数调用Socket(),该函数返回一个整型的S ...
- [Python_7] Python Socket 编程
0. 说明 Python Socket 编程 1. TCP 协议 [TCP Server] 通过 netstat -ano 查看端口是否开启 # -*-coding:utf-8-*-"&qu ...
- C# Socket编程(5)使用TCP Socket
TCP 协议(Transmission Control Protocol,传输控制协议)是TCP/IP体系中面向连接(connection oriented)的传输层(transport layer) ...
- 【.Net MF网络开发板研究-04】Socket编程之服务端
前几篇文章介绍了Http相关的应用,其实从技术角度而言,应该先介绍Socket编程,然后再介绍Http,毕竟Http是用Socket相关函数编程实现的. .NET Micro Framework的So ...
- socket编程缓冲区大小对send()的影响
1. 概述 Socket编程中,使用send()传送数据时,返回结果受到以下几个因素的影响: • Blocking模式或non-blocking模式 • 发送缓冲区的大小 • 接收窗口大小 本文档介绍 ...
- 华中科技大学计算机通信与网络实验,华中科技大学计算机通信与网络实验报告Socket编程实验.docx...
实验一 Socket编程实验 1.1环境 开发环境:Windows 10 64 位,Intel Core i5-7300HQ CPU, 8GB 内存 1.1. 1开发平台 Microsoft Visu ...
- socket recv 服务端阻塞 python_网络编程(基于socket编程)
网络编程(基于socket编程) socket套接字:应用程序通常通过socket"套接字"向网络发送请求或应答网络请求,是主机间或同一计算机中的进程间相互通讯 socket是介于 ...
- python的socket编程_Python Socket编程详细介绍
在使用Python做socket编程时,由于需要使用阻塞(默认)的方式来读取数据流,此时对于数据的结束每次都需要自己处理,太麻烦.并且网上也没找到太好的封装,所以就自己写了个简单的封装. 封装思路 1 ...
最新文章
- 前后台使用ajax传list的时候,用value[] 获取值
- Linux 多线程同步机制:互斥量、信号量、条件变量
- 【计算理论】计算理论总结 ( P 、NP 、NPC 总结 ) ★★
- java web开发学习手册_Java 人必备学习手册开发下载!
- CUDA 网格级并发-流(2)
- 同义词林Java如何更新维护,solr词库实时更新维护
- Round 2—算法的复杂度
- 穿过已知点画平滑曲线-lua
- Linux系统无法在spyder5中输入中文的解决办法
- 计算机音乐谱一壶老酒,一壶老酒简谱(歌词)-陆树铭演唱-沈公宝曲谱
- 计算机游戏图形是什么意思,专业图形显卡和游戏显卡区别
- Android 布局圆角方案总结
- CrowdHuman数据集介绍
- 异常 exception
- C#语言基础学习笔记
- 三四十岁的大龄程序员,如何保持自己的职场核心竞争力?
- 基于WebSocket的在线聊天室
- 修复被劫持、篡改的IE主页
- 又一款社交软件黑马,微信缺的它都有!
- 从科研切入点到方法论创新、从选刊投稿到写作方法,详细聊聊如何完成一篇学术论文的写作
热门文章
- LeetCode 2100. 适合种地的日子(计数)
- LeetCode 1764. 通过连接另一个数组的子数组得到一个数组
- LeetCode MySQL 608. 树节点
- 剑指Offer - 面试题54. 二叉搜索树的第k大节点(二叉树循环遍历)
- LeetCode 470. 用 Rand7() 实现 Rand10()(随机概率)
- LeetCode 701. 二叉搜索树中的插入操作(二叉查找树/插入)
- linux shell 输出日期格式,Linux下Shell日期的格式
- python自动化pdf报告_[Python] 自动化办公 PDF提取文字、表格、图片
- unity game和scene效果不一样_不同的真石漆装饰效果也是不一样的
- python接活网站_python能自己接活_python开发接活 - CSDN