iOS 之 Socket学习

Socket Learning

Posted by Elliot on August 29, 2015

版权声明:本文为博主原创文章,未经博主允许不得转载

公司项目有用到socket,所以这几天学习了下,以前做的网络这块都是用的HTTP,也就是请求–响应这种应答式的方式。这种的话如果是比较小的项目还是蛮合适的,能够节省资源。但是比较大的项目的话就比较劣势了,而用socket就比较好,因为项目中网络请求比较多,时不时的需要发请求,socket的响应速度比HTTP要快,用户体验会要好很多。

Socket其实就是tcp连接,当客户端与服务端三次握手之后就一直连着,所以他的响应速度会比HTTP的应答式快。

C语言 socket

研究了一下C语言的socket,直接上代码,下面演示一个一对一的socket服务端对客户端的demo:

服务端:

  • 首先是sockaddr_in 这个结构体是用来保存socket地址的,具体参数基本看得懂
  • 接下来是创建socket,使用的是socket()这个API创建的,具体参数以后在讲
  • 然后是socket绑定地址也就是刚刚创建的sockaddr_in这个结构体,用bind()这个API绑定
  • 接下来是监听这个socket了,用listen()这个API,注意监听的socket是只用来监听的,不能用来读写数据
  • 监听之后就是新建一个socket用来读写数据了,用accept()这个API
  • 最后就是读写数据嘛,分别用send(),recv()者两个API

代码如下:

/*
 C语言  socket
 */
	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);
	//创建socket
	int 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);
	}

客户端:

  • 基本步骤和服务端一样,
  • 首先保存创建socket地址,不同的是客户端需要写服务端的IP地址
  • 然后是创建socket,绑定地址
  • 接下来和服务端不同,需要连接服务端,所以用到connect()这个API来连接服务端
  • 连接之后就是读写数据了

代码如下:

/*
 C语言 socket
 */
	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");
			}
		}
	}

OC的socket

上面讲的是C语言的socket,下面来讲一下OC的socket,其实很多都是C的,不过是对其进行了一层封装

首先服务端:

  • 基本步骤和C语言的一样
  • 创建socket用的是CFSocketCreate();基本参数见代码
  • 然后是创建保存地址
  • 然后是绑定地址,用的是CFSocketSetAddress()这个API
  • 然后就是和C语言不同的地方了,OC由于有runloop,不向C语言一样需要自己写一个死循环,所以需要将socket加入runloop中去,不断的监听
  • 接下来是回调,这个是在创建socket的时候绑定的回调,同样需要重新创建一个socket来管理数据的读写,不过OC可以直接交给CFSocketNativeHandle这个类去处理,同样的数据流也有CFStream这个类去处理,他的子类可以分别管理读写流

代码如下:

-(int)setupSocket
{
	CFSocketContext sockContext = {0, // 结构体的版本,必须为0
		(__bridge void *)(self),
		NULL, // 一个定义在上面指针中的retain的回调, 可以为NULL
		NULL,
		NULL};
	_socket = CFSocketCreate(kCFAllocatorDefault, // 为新对象分配内存,可以为nil
							 PF_INET, // 协议族,如果为0或者负数,则默认为PF_INET
							 SOCK_STREAM, // 套接字类型,如果协议族为PF_INET,则它会默认为SOCK_STREAM
							 IPPROTO_TCP, // 套接字协议,如果协议族是PF_INET且协议是0或者负数,它会默认为IPPROTO_TCP
							 kCFSocketAcceptCallBack, // 触发回调函数的socket消息类型,具体见Callback Types
							 SocketConnectionAcceptedCallBack, // 上面情况下触发的回调函数
							 &sockContext // 一个持有CFSocket结构信息的对象,可以为nil
							 );
	if (NULL == _socket) {
		NSLog(@"Cannot create socket!");
		return 0;
	}
	int optval = 1;
	setsockopt(CFSocketGetNative(_socket), SOL_SOCKET, SO_REUSEADDR, // 允许重用本地地址和端口
			   (void *)&optval, sizeof(optval));
	struct sockaddr_in addr4;
	memset(&addr4, 0, sizeof(addr4));
	addr4.sin_len = sizeof(addr4);
	addr4.sin_family = AF_INET;
	addr4.sin_port = htons(8080);
	addr4.sin_addr.s_addr = htonl(INADDR_ANY);
	CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&addr4, sizeof(addr4));
	if (kCFSocketSuccess != CFSocketSetAddress(_socket, address))
	{
		NSLog(@"Bind to address failed!");
		if (_socket)
			CFRelease(_socket);
		_socket = NULL;
		return 0;
	}
	CFRunLoopRef cfRunLoop = CFRunLoopGetCurrent();
	CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socket, 0);
	CFRunLoopAddSource(cfRunLoop, source, kCFRunLoopCommonModes);
	CFRunLoopRun();
	CFRelease(source);
	return 1;
}

//回调
static void SocketConnectionAcceptedCallBack(CFSocketRef socket,
											 CFSocketCallBackType type,
											 CFDataRef address,
											 const void *data, void *info) {
	if (kCFSocketAcceptCallBack == type)
	{
		// 本地套接字句柄
		CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
		uint8_t name[SOCK_MAXADDRLEN];
		socklen_t nameLen = sizeof(name);
		if (0 != getpeername(nativeSocketHandle, (struct sockaddr *)name, &nameLen)) {
			NSLog(@"error");
			exit(1);
		}
		CFReadStreamRef iStream;
		CFWriteStreamRef oStream;
		// 创建一个可读写的socket连接
		CFStreamCreatePairWithSocket(kCFAllocatorDefault, nativeSocketHandle, &iStream, &oStream);
		if (iStream && oStream)
		{
			CFStreamClientContext streamContext = {0, info, NULL, NULL};
			if (!CFReadStreamSetClient(iStream, kCFStreamEventHasBytesAvailable,readStream, &streamContext))
			{
				exit(1);
			}

			if (!CFWriteStreamSetClient(oStream, kCFStreamEventCanAcceptBytes, writeStream, &streamContext))
			{
				exit(1);
			}
			CFReadStreamScheduleWithRunLoop(iStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
			CFWriteStreamScheduleWithRunLoop(oStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
			CFReadStreamOpen(iStream);
			CFWriteStreamOpen(oStream);
		} else
		{
			close(nativeSocketHandle);
		}
	}
}
// 读取数据
void readStream(CFReadStreamRef stream, CFStreamEventType eventType, void *clientCallBackInfo)
{
	UInt8 buff[1024*4];

	NSMutableData *recvData = [NSMutableData data];

	while (1) {
		CFIndex count = CFReadStreamRead(stream, buff, 1024*4);
		if (count==0 || count == -1) {

		}else{
			[recvData appendBytes:buff length:count];
			if (!CFReadStreamHasBytesAvailable(stream)) {
				break;
			}
		}
	}
	NSString *recvString = [[NSString alloc]initWithData:recvData encoding:NSUTF8StringEncoding];
	///根据delegate显示到主界面去
	NSString *strMsg = [[NSString alloc]initWithFormat:@"客户端传来消息:%@",recvString];
//	CSocketServer *info = (__bridge CSocketServer*)clientCallBackInfo;
//	[info ShowMsgOnMainPage:strMsg];
	NSLog(@"%@",strMsg);

//	char *str = "你好 Client";
	NSString *string = @"你好 Client\n";
	NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
	NSMutableData *data1 = [NSMutableData dataWithData:data];
//	uint8_t * uin8b = (uint8_t *)str;
	const uint8_t * uin8b = data1.bytes;
	if (outputStream != NULL)
	{

		while (1) {
			if (data1.length>0 && CFWriteStreamCanAcceptBytes(outputStream)) {
				CFIndex writeLength = CFWriteStreamWrite(outputStream, uin8b, data1.length);

				if (writeLength == 0 || writeLength == -1) {

				}else{
					[data1 replaceBytesInRange:NSMakeRange(0, writeLength) withBytes:NULL length:0];
				}
			}else{
				break;
			}

		}
	}
	else {
		NSLog(@"Cannot send data!");
	}

}
void writeStream (CFWriteStreamRef stream, CFStreamEventType eventType, void *clientCallBackInfo)
{
	outputStream = stream;

}

客户端: 步骤和服务端差不多,直接上代码:

-(void)CreateConnect:(NSString*)strAddress
{
	CFSocketContext sockContext = {0, // 结构体的版本,必须为0
		(__bridge void *)(self),
		NULL, // 一个定义在上面指针中的retain的回调, 可以为NULL
		NULL,
		NULL};
	_socket = CFSocketCreate(kCFAllocatorDefault, // 为新对象分配内存,可以为nil
							 PF_INET, // 协议族,如果为0或者负数,则默认为PF_INET
							 SOCK_STREAM, // 套接字类型,如果协议族为PF_INET,则它会默认为SOCK_STREAM
							 IPPROTO_TCP, // 套接字协议,如果协议族是PF_INET且协议是0或者负数,它会默认为IPPROTO_TCP
							 kCFSocketConnectCallBack, // 触发回调函数的socket消息类型,具体见Callback Types
							 TCPClientConnectCallBack, // 上面情况下触发的回调函数
							 &sockContext // 一个持有CFSocket结构信息的对象,可以为nil
							 );
	if(_socket != NULL)
	{
		struct sockaddr_in addr4;   // IPV4
		memset(&addr4, 0, sizeof(addr4));
		addr4.sin_len = sizeof(addr4);
		addr4.sin_family = AF_INET;
		addr4.sin_port = htons(8080);
		addr4.sin_addr.s_addr = inet_addr([strAddress UTF8String]);  // 把字符串的地址转换为机器可识别的网络地址

		// 把sockaddr_in结构体中的地址转换为Data
		CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&addr4, sizeof(addr4));
		CFSocketConnectToAddress(_socket, // 连接的socket
								 address, // CFDataRef类型的包含上面socket的远程地址的对象
								 -1  // 连接超时时间,如果为负,则不尝试连接,而是把连接放在后台进行,如果_socket消息类型为kCFSocketConnectCallBack,将会在连接成功或失败的时候在后台触发回调函数
								 );
		CFRunLoopRef cRunRef = CFRunLoopGetCurrent();    // 获取当前线程的循环
		// 创建一个循环,但并没有真正加如到循环中,需要调用CFRunLoopAddSource
		CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socket, 0);
		CFRunLoopAddSource(cRunRef, // 运行循环
						   sourceRef,  // 增加的运行循环源, 它会被retain一次
						   kCFRunLoopCommonModes  // 增加的运行循环源的模式
						   );
		CFRelease(sourceRef);
		NSLog(@"connect ok");
	}
}
// socket回调函数,同客户端
static void TCPClientConnectCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
{
	if (kCFSocketAcceptCallBack == type)
	{
		// 本地套接字句柄
		CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
		uint8_t name[SOCK_MAXADDRLEN];
		socklen_t nameLen = sizeof(name);
		if (0 != getpeername(nativeSocketHandle, (struct sockaddr *)name, &nameLen)) {
			NSLog(@"error");
			exit(1);
		}
		CFReadStreamRef iStream;
		CFWriteStreamRef oStream;
		// 创建一个可读写的socket连接
		CFStreamCreatePairWithSocket(kCFAllocatorDefault, nativeSocketHandle, &iStream, &oStream);
		if (iStream && oStream)
		{
			CFStreamClientContext streamContext = {0, info, NULL, NULL};
			if (!CFReadStreamSetClient(iStream, kCFStreamEventHasBytesAvailable,readStream, &streamContext))
			{
				exit(1);
			}
			if (!CFWriteStreamSetClient(oStream, kCFStreamEventCanAcceptBytes, writeStream, &streamContext))
			{
				exit(1);
			}
			CFReadStreamScheduleWithRunLoop(iStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
			CFWriteStreamScheduleWithRunLoop(oStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
			CFReadStreamOpen(iStream);
			CFWriteStreamOpen(oStream);
		} else
		{
			close(nativeSocketHandle);
		}
	}
}

// 读取数据
void readStream(CFReadStreamRef stream, CFStreamEventType eventType, void *clientCallBackInfo)
{
	UInt8 buff[1024*4];

	NSMutableData *recvData = [NSMutableData data];

	while (1) {
		CFIndex count = CFReadStreamRead(stream, buff, 1024*4);
		if (count==0 || count == -1) {

		}else{
			[recvData appendBytes:buff length:count];
			if (!CFReadStreamHasBytesAvailable(stream)) {
				break;
			}
		}
	}
	NSString *recvString = [[NSString alloc]initWithData:recvData encoding:NSUTF8StringEncoding];
	///根据delegate显示到主界面去
	NSString *strMsg = [[NSString alloc]initWithFormat:@"客户端传来消息:%@",recvString];


	//	CSocketServer *info = (__bridge CSocketServer*)clientCallBackInfo;
	//	[info ShowMsgOnMainPage:strMsg];
	NSLog(@"%@",strMsg);

	//	char *str = "你好 Client";
	NSString *string = @"你好 Client\n";
	NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];


	NSMutableData *data1 = [NSMutableData dataWithData:data];
	//	uint8_t * uin8b = (uint8_t *)str;
	const uint8_t * uin8b = data1.bytes;
	if (outputStream != NULL)
	{

		while (1) {
			if (data1.length>0 && CFWriteStreamCanAcceptBytes(outputStream)) {
				CFIndex writeLength = CFWriteStreamWrite(outputStream, uin8b, data1.length);

				if (writeLength == 0 || writeLength == -1) {

				}else{
					[data1 replaceBytesInRange:NSMakeRange(0, writeLength) withBytes:NULL length:0];

				}
			}else{
				break;
			}

		}
	}
	else {
		NSLog(@"Cannot send data!");
	}

}
void writeStream (CFWriteStreamRef stream, CFStreamEventType eventType, void *clientCallBackInfo)
{
	outputStream = stream;

}

AsycSocket浅析

Socket基本就是这样了,还有一个牛人封装好的一个socket,用起来确实简单多了,不过坏处是有很多不需要的代码,所以需要自己去优化,也直接上代码吧:

服务端:

-(void)start{
	asyncSocket = [[AsyncSocket alloc] initWithDelegate:self];
	NSError *err = nil;
	if (![asyncSocket acceptOnPort:8080 error:&err]) {
		return;
	}
	NSLog(@"开始监听");
}

- (void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket
{
	NSLog(@"%s", __func__);
	self.clientAsycSocket = newSocket;

}
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port{
	NSLog(@"%s",__func__);

	[sock readDataWithTimeout:-1 tag:0];

	NSData *aData=[@"Hi there  server" dataUsingEncoding:NSUTF8StringEncoding];
	[sock writeData:aData withTimeout:-1 tag:0];
}


//读取数据
-(void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
	NSString *aStr=[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
	NSLog(@"aStr==%@",aStr);
}
//是否加密
-(void)onSocketDidSecure:(AsyncSocket *)sock
{
	NSLog(@"onSocket:%p did go a secure line:YES",sock);
}
//遇到错误时关闭连接
-(void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err
{
	NSLog(@"onSocket:%p will disconnect with error:%@",sock,err);
}
//断开连接
-(void)onSocketDidDisconnect:(AsyncSocket *)sock
{
	NSLog(@"onSocketDidDisconnect:%p",sock);
}
- (void )dealloc
{
	NSLog(@"%s", __func__);
}

客户端代码:

-(void)start{
	asyncSocket = [[AsyncSocket alloc] initWithDelegate:self];
	NSError *err = nil;
	if(![asyncSocket connectToHost:@"127.0.0.1" onPort:8080 error:&err])
	{
		NSLog(@"Error: %@", err);
	}
//	NSData *aData=[@"Hi there" dataUsingEncoding:NSUTF8StringEncoding];
//	[asyncSocket writeData:aData withTimeout:-1 tag:0];
}

//建立连接
-(void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
{
	NSLog(@"onScoket:%p did connecte to host:%@ on port:%d",sock,host,port);

//	NSData *aData=[@"Hi there" dataUsingEncoding:NSUTF8StringEncoding];
//	[sock writeData:aData withTimeout:-1 tag:0];
	[sock readDataWithTimeout:-1 tag:0];
}

//读取数据
-(void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
	NSString *aStr=[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
	NSLog(@"aStr==%@",aStr);
	NSData *aData=[@"Hi there" dataUsingEncoding:NSUTF8StringEncoding];
	[sock writeData:aData withTimeout:-1 tag:0];
	[sock readDataWithTimeout:-1 tag:0];
}
//是否加密
-(void)onSocketDidSecure:(AsyncSocket *)sock
{
	NSLog(@"onSocket:%p did go a secure line:YES",sock);
}
//遇到错误时关闭连接
-(void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err
{
	NSLog(@"onSocket:%p will disconnect with error:%@",sock,err);
}
//断开连接
-(void)onSocketDidDisconnect:(AsyncSocket *)sock
{
	NSLog(@"onSocketDidDisconnect:%p",sock);
}

好啦,Socket学习笔记到此结束。 demo在此Github