BSD socket API
/**
socket 创建并初始化 socket,返回该 socket 的文件描述符,如果描述符为 -1 表示创建失败。
@param addressFamily 是 IPv4(AF_INET) 或 IPv6(AF_INET6)。
@param type 表示 socket 的类型,通常是流stream(SOCK_STREAM) 或数据报文datagram(SOCK_DGRAM)
@param protocol 参数通常设置为0,以便让系统自动为选择我们合适的协议,对于 stream socket 来说会是 TCP 协议(IPPROTO_TCP),而对于 datagram来说会是 UDP 协议(IPPROTO_UDP)。
@return 返回该 socket 的文件描述符
*//
int socket(int addressFamily, int type, int protocol);
/**
服务器端侦听客户端的请求
@param socketFileDescriptor 服务端socket
@param backlogSize 客户端连接请求缓冲区队列的大小
@return 0 成功或者其他 错误代号,(不是非0即真)
*/
int listen(int socketFileDescriptor, int backlogSize) __DARWIN_ALIAS(listen);
/**
接受客户端连接请求并将客户端的网络地址信息保存到 clientAddress 中。
当客户端连接请求被服务器接受之后,客户端和服务器之间的链路就建立好了,两者就可以通信了
@param socketFileDescriptor 服务器的socket描述字
@param address 指向struct sockaddr *的指针,用于返回客户端的协议地址
@param addressStructLength address结构体数据长度
@return 由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。
*/
int accept(int socketFileDescriptor, struct sockaddr * __restrict address, socklen_t * __restrict addressStructLength)
__DARWIN_ALIAS_C(accept);
/**
客户端向特定网络地址的服务器发送连接请求,连接成功返回0,失败返回 -1。
当服务器建立好之后,客户端通过调用该接口向服务器发起建立连接请求。对于 UDP 来说,该接口是可选的,如果调用了该接口,表明设置了该 UDP socket 默认的网络地址。对 TCP socket来说这就是传说中三次握手建立连接发生的地。
注意:该接口调用会阻塞当前线程,直到服务器返回。
@param socketFileDescriptor 客户端sockets
@param serverAddress 向数据结构sockaddr的指针,其中包括目的端口和IP地址,服务器的"结构体"地址;提示:C 语言中没有对象
@param serverAddressLength 结构体数据长度
@return 0 成功或者其他 错误代号,(不是非0即真)
*/
int connect(int socketFileDescriptor, const struct sockaddr *serverAddress, socklen_t serverAddressLength) __DARWIN_ALIAS_C(connect);
/**
从 socket 中读取数据,读取成功返回成功读取的字节数,否则返回 -1。
一旦连接建立好之后,就可以通过 send或者receive 接口发送或接收数据了。注意调用 connect 设置了默认网络地址的 UDP socket 也可以调用该接口来发送数据。
@param socketFileDescriptor 客户端sockets
@param buf 接受数据的buffer
@param bufferLength buffer长度
@param flags 接收方式,0表示阻塞,必须等待服务器返回数据
@return 如果成功,则返回读入的字节数,失败则返回SOCKET_ERROR
*/
ssize_t recv(int socketFileDescriptor, void *buf, size_t bufferLength, int flags) __DARWIN_ALIAS_C(recv);
/**
通过 socket 发送数据,发送成功返回成功发送的字节数,否则返回 -1。
一旦连接建立好之后,就可以通过 send/receive 接口发送或接收数据了。注意调用 connect 设置了默认网络地址的 UDP socket 也可以调用该接口来接收数据。
@param socketFileDescriptor 指定发送端套接字描述符
@param buf 存放应用程序要发送数据的缓冲区
@param bufferLength 发送的数据的字节数
@param flags 发送方式,0表示阻塞,必须等待服务器返回数据
@return 如果成功,则返回发送的字节数,失败则返回SOCKET_ERROR
*/
ssize_t send(int socketFileDescriptor, const void *buf, size_t bufferLength, int flags) __DARWIN_ALIAS_C(send);
/**
将 socket 与特定主机地址与端口号绑定,成功绑定返回0,失败返回 -1。
成功绑定之后,根据协议(TCPUDP)的不同,我们可以对 socket 进行不同的操作:
UDP:因为 UDP 是无连接的,绑定之后就可以利用 UDP socket 传送数据了。
TCP:而 TCP 是需要建立端到端连接的,为了建立 TCP 连接服务器必须调用 listen(int socketFileDescriptor, int backlogSize) 来设置服务器的缓冲区队列以接收客户端的连接请求,backlogSize 表示客户端连接请求缓冲区队列的大小。当调用 listen 设置之后,服务器等待客户端请求,然后调用 accept 来接受客户端的连接请求。
@param socketFileDescriptor 主机socket
@param addressToBind 数据结构sockaddr的指针,其中包括目的端口和IP地址,服务器的"结构体"地址;提示:C 语言中没有对象
@param addressStructLength sockaddr结构体数据长度
@return 0 成功/其他 错误代号,(不是非0即真)
*/
int bind(int socketFileDescriptor, const struct sockaddr *addressToBind, socklen_t addressStructLength) __DARWIN_ALIAS(bind);
/**
服务器端侦听客户端的请求
@param socketFileDescriptor 服务端socket
@param backlogSize 客户端连接请求缓冲区队列的大小
@return 0 成功/其他 错误代号,(不是非0即真)
*/
int listen(int socketFileDescriptor, int backlogSize) __DARWIN_ALIAS(listen);
/**
接受客户端连接请求并将客户端的网络地址信息保存到 clientAddress 中。
当客户端连接请求被服务器接受之后,客户端和服务器之间的链路就建立好了,两者就可以通信了
@param socketFileDescriptor 服务器的socket描述字
@param address 指向struct sockaddr *的指针,用于返回客户端的协议地址
@param addressStructLength address结构体数据长度
@return 由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。
*/
int accept(int socketFileDescriptor, struct sockaddr * __restrict address, socklen_t * __restrict addressStructLength)
__DARWIN_ALIAS_C(accept);
一、客户端操作
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
server_addr.sin_len = sizeof(struct sockaddr_in);// 结构体长度
server_addr.sin_family = AF_INET; //sin_family指代协议族,在socket编程中只能是AF_INET
server_addr.sin_port = htons(1234);//存储端口号(使用网络字节顺序),在linux下,端口号的范围0~65535,同时0~1024范围的端口号已经被系统使用或保留。
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");//存储IP地址,使用in_addr这个数据结构
bzero(&(server_addr.sin_zero), 8);//是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节, 初始值应该使用函数 bzero() 来全部置零
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
//创建新的socket
int aResult = connect(server_socket, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in));
if (aResult == -1) {
NSLog(@"链接失败");
}else{
self.server_socket = server_socket;
[self acceptFromServer];
}
});
- (void)acceptFromServer {
while (1) {
//接受服务器传来的数据
char buf[1024];
long iReturn = recv(self.server_socket, buf, 1024, 0);
if (iReturn > 0) {
NSString *str = [NSString stringWithCString:buf encoding:NSUTF8StringEncoding];
//筛选前缀
if ([str hasPrefix:@"list:"]) {
NSString *arrayStr = [str substringFromIndex:5];
NSArray *list = [arrayStr componentsSeparatedByString:@","];
self.userArray = [NSMutableArray arrayWithArray:list];
dispatch_async(dispatch_get_main_queue(), ^{
[self.onlineTable reloadData];
});
NSLog(@"当前在线用户列表:%@",arrayStr);
}else{
//回到主线程 界面上显示内容
[self showLogsWithString:str];
}
}else if (iReturn == -1){
NSLog(@"接受失败-1");
break;
}
}
}
- (void)sendMsg:(NSString*)msg {
char *buf[1024] = {0};
const char *p1 = (char*)buf;
p1 = [msg cStringUsingEncoding:NSUTF8StringEncoding];
send(self.server_socket, p1, 1024, 0);
}
二、服务端操作
//创建socket
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1) {
NSLog(@"创建失败");
[self showLogsWithString:@"socket创建失败"];
} else {
//绑定地址和端口
struct sockaddr_in server_addr;
server_addr.sin_len = sizeof(struct sockaddr_in);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(1234);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
bzero(&(server_addr.sin_zero), 8);
int bind_result = bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (bind_result == -1) {
NSLog(@"绑定端口失败");
[self showLogsWithString:@"绑定端口失败"];
} else {
if (listen(server_socket, kMaxConnectCount)==-1) {
NSLog(@"监听失败");
[self showLogsWithString:@"监听失败"];
} else {
for (int i = 0; i < kMaxConnectCount; i++) {
//接受客户端的链接
[self acceptClientWithServerSocket:server_socket];
}
}
}
}
- (void)acceptClientWithServerSocket:(int)server_socket {
struct sockaddr_in client_address;
socklen_t address_len;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
//创建新的socket
while (1) {
int client_socket = accept(server_socket, (struct sockaddr*)&client_address,&address_len );
if (client_socket == -1) {
[self showLogsWithString:@"接受客户端链接失败"];
NSLog(@"接受客户端链接失败");
}else{
NSString *acceptInfo = [NSString stringWithFormat:@"客户端 in,socket:%d",client_socket];
[self showLogsWithString:acceptInfo];
//接受客户端数据
[self recvFromClinetWithSocket:client_socket];
}
}
});
}
- (void)recvFromClinetWithSocket:(int)client_socket{
while (1) {
//接受客户端传来的数据
char buf[1024] = {0};
long iReturn = recv(client_socket, buf, 1024, 0);
if (iReturn > 0) {
NSLog(@"客户端来消息了");
NSString *str = [NSString stringWithCString:buf encoding:NSUTF8StringEncoding];
[self showLogsWithString:[NSString stringWithFormat:@"客户端来消息了:%@",str]];
[self checkRecvStr:str andClientSocket:client_socket];
} else if (iReturn == -1) {
NSLog(@"读取消息失败");
[self showLogsWithString:@"读取消息失败"];
break;
} else if (iReturn == 0) {
NSLog(@"客户端走了");
[self showLogsWithString:[NSString stringWithFormat:@"客户端 out socket:%d",client_socket]];
NSMutableArray *array = [NSMutableArray arrayWithArray:self.clientArray];
for (ClientModel *model in array) {
if (model.clientSocket == client_socket) {
[self.clientNameArray removeObject:model.clientName];
[self.clientArray removeObject:model];
}
}
close(client_socket);
break;
}
}
}