Socket,是基于网络通信协议封装的一个网络通信接口,可以支持TCP或UDP通信协议。通常是应用于TCP协议,这时的socket连接就是一个TCP连接,也遵循TCP建立连接时的“三次握手”。
Socket想要建立连接是需要一个服务器端的ServerSocket和一个客户端的ClientSocket,即需要一对套接字。套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。连接确认后就可以进行socket通信了。(关于网络通信及协议不太了解的同学可以移步这里 --> 相关知识)接下来,我们需要的是建立一对套接字。
首先是创建ServerSocket,本次是以Mac端模拟服务器端,创建一个Command Line Tool工程,并在创建完成之后保持连接,等待客户端请求。
主要过程:
一: 创建CFSocket
二:设置服务器本地的地址和端口
三:绑定CFSocket到本地地址
四:将CFSocket包装成source添加到runloop里面
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 一: 创建CFSocket,监听socket的连接,指定TCPServerAcceptCallBack函数为连接回调函数
CFSocketRef _socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, TCPServerAcceptCallBack, NULL);
if (_socket == NULL) {
NSLog(@"创建socket失败");
return 0;
}
int optval = 1;
// 二:设置允许重用本地地址和端口,并配置端口信息
setsockopt(CFSocketGetNative(_socket), SOL_SOCKET, SO_REUSEADDR, (void*)&optval, sizeof(optval));
// 定义socket地址
struct sockaddr_in addr4;
memset(&addr4, 0, sizeof(addr4));
addr4.sin_len = sizeof(addr4);
addr4.sin_family = AF_INET;
// 设置本服务器可以监听本机任意可用 的IP地址
// addr4.sin_addr.s_addr = htonl(INADDR_ANY);
// 设置服务器的监听地址,为本机的地址
addr4.sin_addr.s_addr = inet_addr("xx.xx.xx.xx");
// 设置服务器监听端口
addr4.sin_port = htons(30000);
// 将ipv4转换为cfdataref
CFDataRef address = CFDataCreate(kCFAllocatorDefault, (uint8 *)&addr4, sizeof(addr4));
// 三: 将CFSocket通过CFSocketSetAddress函数绑定到指定的IP
if (CFSocketSetAddress(_socket, address) != kCFSocketSuccess) {
NSLog(@"地址绑定失败");
// 如果_socket不为null。释放_socket
if (_socket) {
CFRelease(_socket);
exit(1);
}
_socket = NULL;
}
NSLog(@"启动循环监听客户端连接-----");
// 四: 获取当前线程的runloop,并将CFSocket作为source绑定到runloop上,保证持续不断的接受来自客户端的请求(TCP第一次握手)
CFRunLoopRef cfrunloop = CFRunLoopGetCurrent();
// 将socket包装成cfrunloopsource
CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socket, 0);
// 为runloop添加source
CFRunLoopAddSource(cfrunloop, source, kCFRunLoopCommonModes);
CFRelease(source);
CFRunLoopRun();
}
return 0;
}
说明:当本地地址是通过WiFi分配的时候,在重新连接WiFi后地址将会发生变化,所以也可以自动获取本机的地址,相关链接为 -->代码获取本机IP地址
上面函数涉及到的TCPServerAcceptCallBack回调函数如下
void TCPServerAcceptCallBack(CFSocketRef socket,CFSocketCallBackType type,CFDataRef address,const void *data,void *info)
{
//如果有客户端连接进来
if (kCFSocketAcceptCallBack == type) {
// 本地socket的handle
CFSocketNativeHandle nativaSocketHandle = *(CFSocketNativeHandle *)data;
uint8_t name[SOCK_MAXADDRLEN];
socklen_t nameLen = sizeof(name);
// 获取对方的socket信息,和本程序的socket信息
if (getpeername(nativaSocketHandle, (struct sockaddr*)name, &nameLen) != 0) {
NSLog(@"error");
exit(1);
}
// 获取连接信息
struct sockaddr_in *add_in = (struct sockaddr_in *)name;
NSLog(@"%s : %d",inet_ntoa(add_in -> sin_addr),add_in -> sin_port);
CFReadStreamRef iStream;
// 创建一个可读写的CFStream
CFStreamCreatePairWithSocket(kCFAllocatorDefault, nativaSocketHandle, &iStream, &oStream);
if (iStream && oStream) {
// 打开输入流和输出流
CFReadStreamOpen(iStream);
CFWriteStreamOpen(oStream);
CFStreamClientContext streamConText = {0,NULL,NULL,NULL};
// readStream函数为有可读数据时候调用
if (!CFReadStreamSetClient(iStream, kCFStreamEventHasBytesAvailable, readStream, &streamConText)) {
exit(1);
}
CFReadStreamScheduleWithRunLoop(iStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
const char *str = "您好,您已成功连接\n";
// 向客户端输出数据
CFWriteStreamWrite(oStream, (uint8 *)str, strlen(str) + 1);
}
}
}
当有可读信息传到的时候调用readStream函数
void readStream (CFReadStreamRef iStream,CFStreamEventType eventType,void *clientCallBackInfo)
{
uint8 buff[2048];
CFIndex hasRead = CFReadStreamRead(iStream, buff, 2048);
if (hasRead > 0) {
buff[hasRead] = '\0';
printf("接收到数据:%s\n",buff);
const char *str = (char *)buff;
// 向客户端输出数据
CFWriteStreamWrite(oStream, (uint8 *)str, strlen(str) + 1);
}
}
此时运行程序可看到:
其次,创建客户端ClientSocket,对服务器进行连接,发送数据
一: 创建CFSocket
_socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketNoCallBack, nil, NULL);
二: CFSocket创建成功后,定义socket连接地址
// 定义sockadd_in 作为cfsocket的地址
struct sockaddr_in addr4;
memset(&addr4, 0, sizeof(addr4));
addr4.sin_len = sizeof(addr4);
addr4.sin_family = AF_INET;
// 设置远程服务器地址
addr4.sin_addr.s_addr = inet_addr("xx.xx.xx.xx");
addr4.sin_port = htons(30000);
CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&addr4, sizeof(addr4));
三: 连接服务器,成功后新线程内通过readStream相应服务器返回的数据
CFSocketError result = CFSocketConnectToAddress(_socket, address, 5);
if (result == kCFSocketSuccess) {
isOnline = YES;
[NSThread detachNewThreadSelector:@selector(readStream) toTarget:self withObject:nil];
}
客户端readStream方法
- (void)readStream
{
char buffer[2048];
int hadRead;
// 与本机相连的socket如果已经失效,则返回-1
while (hadRead = recv(CFSocketGetNative(_socket), buffer, sizeof(buffer), 0)) {
NSString *contend = [[NSString alloc] initWithBytes:buffer length:hadRead encoding:NSUTF8StringEncoding];
dispatch_async(dispatch_get_main_queue(), ^{
self.showView.text = [NSString stringWithFormat:@"%@\n%@",contend,self.showView.text];
NSLog(@"%@\n",contend);
});
}
}