Socket其实就是在传输层对TCP/UDP封装的一层API,俗称套接字。 就是为网络服务提供的一种机制,通信的两端都是Socket(用户端和服务端)两个Socket IO传输,Socket是纯C语言的,是跨平台的。下图:
UDP :(用户数据报文协议) 只管发送,不确认对方是否接收,将数据及源和目的的封装成数据包中,不需要建立连接。每个数据报的大小限制在64K之内。因为无需连接,因此是不可靠协议,不需要建立连接,特点就是:速度快。
TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的通信协议,数据在传输前要建立连接,传输完毕后还要断开连接,客户端在收发数据前要使用 connect() 函数和服务器建立连接。建立连接的目的是保证IP地址、端口、物理链路等正确无误,为数据的传输开辟通道。
TCP建立连接时要传输三个数据包,俗称三次握手(Three-way Handshaking),上一篇文章中有讲到。
关于客户端和服务端,服务端只多了bind、listen、accept 这三个函数。
客户端代码:
创建:
/**
1: 创建socket
参数
domain:协议域,又称协议族(family)。常用的协议族有AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域Socket)、AF_ROUTE等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
type:指定Socket类型。常用的socket类型有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等。流式Socket(SOCK_STREAM)是一种面向连接的Socket,针对于面向连接的TCP服务应用。数据报式Socket(SOCK_DGRAM)是一种无连接的Socket,对应于无连接的UDP服务应用。
protocol:指定协议。常用协议有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。
注意:1.type和protocol不可以随意组合,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当第三个参数为0时,会自动选择第二个参数类型对应的默认协议。
返回值:
如果调用成功就返回新创建的套接字的描述符,如果失败就返回INVALID_SOCKET(Linux下失败返回-1)
*/
int socketId = socket(AF_INET, SOCK_STREAM, 0);
self.clinenId = socketId;
if (socketId == -1) {
NSLog(@"创建失败....");
return;
}
/**
__uint8_t sin_len; 假如没有这个成员,其所占的一个字节被并入到sin_family成员中
sa_family_t sin_family; 一般来说AF_INET(地址族)PF_INET(协议族)
in_port_t sin_port; // 端口
struct in_addr sin_addr; // ip
char sin_zero[8]; 没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐
*/
struct sockaddr_in socketAdd;
socketAdd.sin_family = AF_INET;
socketAdd.sin_port = SocketPort;
struct in_addr socket_idAdd;
socket_idAdd.s_addr = SocketIP;
socketAdd.sin_addr = socket_idAdd;
//
/**
参数
参数一:套接字描述符
参数二:指向数据结构sockaddr的指针,其中包括目的端口和IP地址
参数三:参数二sockaddr的长度,可以通过sizeof(struct sockaddr)获得
返回值
成功则返回0,失败返回非0,错误码GetLastError()。
*/
// 连接通道
int result = connect(socketId,(const struct sockaddr *)&socketAdd, sizeof(socketAdd));
if (result != 0) {
NSLog(@"连接失败....");
return;
}
NSLog(@"连接成功");
// 接受消息
self.index = 0;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 这里写接收消息的代码, 如下:
});
接收消息 :
// 4. 接收数据
/**
参数
1> 客户端socket
2> 接收内容缓冲区地址
3> 接收内容缓存区长度
4> 接收方式,0表示阻塞,必须等待服务器返回数据
返回值
如果成功,则返回读入的字节数,失败则返回SOCKET_ERROR
*/
while (1) {
uint8_t buffer[1024];
ssize_t recLen = recv(self.clinenId, buffer, sizeof(buffer), 0);
NSLog(@"接受到了%ld 字节",recLen);
if (recLen == 0) {
self.index ++;
if (self.index > 5) {
return ;
}
}
NSData *data = [NSData dataWithBytes:buffer length:recLen];
NSString *str= [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@",str);
self.index = 0;
dispatch_async(dispatch_get_main_queue(), ^{
// 在主线程更新UI, 消息:str
});
}
发送:
/**
3: 发送消息
s:一个用于标识已连接套接口的描述字。
buf:包含待发送数据的缓冲区。
len:缓冲区中数据的长度。
flags:调用执行方式。
返回值
如果成功,则返回发送的字节数,失败则返回SOCKET_ERROR
一个中文对应 3 个字节!UTF8 编码!
*/
if (self.sendMsgContent_tf.text.length==0) {
NSLog(@"消息为空,无法发送");
return;
}
const char *msg = self.sendMsgContent_tf.text.UTF8String;
// 监控发送数据长度
ssize_t sendLen = send(self.clinenId, msg, strlen(msg), 0);
服务端代码:
#pragma mark - 创建socket建立连接
#define SocketPort htons(8041)
#define SocketIP inet_addr("192.168.0.102")
static int const kMaxConnectCount = 5;
- (void)socketConnetAction {
// 1: 创建socket
self.serverId = socket(AF_INET, SOCK_STREAM, 0);
if (self.serverId == -1) {
NSLog(@"创建socket 失败");
return;
}
NSLog(@"创建socket 成功");
struct sockaddr_in socketAddr;
socketAddr.sin_family = AF_INET;
socketAddr.sin_port = SocketPort;
struct in_addr socketIn_addr;
socketIn_addr.s_addr = SocketIP;
socketAddr.sin_addr = socketIn_addr;
bzero(&(socketAddr.sin_zero), 8);
// 2: 绑定socket
int bind_result = bind(self.serverId, (const struct sockaddr *)&socketAddr, sizeof(socketAddr));
if (bind_result == -1) {
NSLog(@"绑定socket 失败");
return;
}
NSLog(@"绑定socket成功");
// 3: 监听socket
int listen_result = listen(self.serverId, kMaxConnectCount);
if (listen_result == -1) {
NSLog(@"监听失败");
return;
}
NSLog(@"监听成功");
// 4: 接受客户端的链接
for (int i = 0; i < kMaxConnectCount; i++) {
[self acceptClientConnet];
}
// 发送消息同客服端一样
}
#pragma mark - 接受客户端的链接
- (void)acceptClientConnet{
// 阻塞线程
dispatch_async(dispatch_get_global_queue(0, 0), ^{
struct sockaddr_in client_address;
socklen_t address_len;
// accept函数
int client_socket = accept(self.serverId, (struct sockaddr *)&client_address, &address_len);
self.client_socket = client_socket;
if (client_socket == -1) {
NSLog(@"接受 %u 客户端错误",address_len);
}else{
NSString *acceptInfo = [NSString stringWithFormat:@"客户端 in,socket:%d",client_socket];
NSLog(@" 接收到的消息%@",acceptInfo);
}
});
}