一、Socket原理
1.套接字(Socket)概念
套接字(Socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口
。
应用层通过传输层进行数据通信时,TCP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了套接字(Socket)接口。应用层可以和传输层通过Socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。
2.建立Socket连接
建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket,另一个运行于服务器端,称为ServerSocket。
套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。
服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
Socket可以支持不同的传输层协议(TCP/UDP),当使用TCP协议进行连接是,该socket连接就是一个TCP连接,UDP同理。
二、使用
Core Foundation提供操作socket的方法
CFNetwork、CFScoket、BSD Scoket。
CFScoket
OS官方给出的CFSocket,它是基于BSD Socket进行抽象和封装,CFSocket中包含了少数开销,它几乎可以提供BSD sockets 所具有的一切功能,并且把socket集成进一个“运行循环”当中。CFSocket并不仅仅限于流的sockets(比如TCP),它可以处理任何类型的socket。
//连接服务器
-(void)connectServer{
if (!_socketRef) {
// 创建socket关联的上下文信息
/*
typedef struct {
CFIndex version; 版本号, 必须为0
void * info; 一个指向任意程序定义数据的指针,可以在CFSocket对象刚创建的时候与之关联,被传递给所有在上下文中回调
const void *(*retain)(const void *info); info 指针中的retain回调,可以为NULL
void (*release)(const void *info); info指针中的release回调,可以为NULL
CFStringRef (*copyDescription)(const void *info); 回调描述,可以n为NULL
} CFSocketContext;
*/
CFSocketContext sockContext = {0, (__bridge void *)(self), NULL, NULL, NULL};
//创建一个socket
_socketRef = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCF, kCFSocketConnectCallBack, ServerConnectCallBack, &sockContext);
//创建sockadd_in的结构体,改结构体作为socket的地址,IPV6需要改参数
//sockaddr_in
// sin_len; 长度
//sin_family;协议簇, 用AF_INET -> 互联网络, TCP,UDP 等等
//sin_port; 端口号(使用网络字节顺序)htons:将主机的无符号短整形数转成网络字节顺序
//in_addr sin_addr; 存储IP地址, inet_addr()的功能是将一个点分十进制的IP转换成一个长整型数(u_long类型),若字符串有效则将字符串转换为32位二进制网络字节序的IPV4地址, 否则为IMADDR_NONE
//sin_zero[8]; 让sockaddr与sockaddr_in 两个数据结构保持大小相同而保留的空字节,无需处理
struct sockaddr_in Socketaddr;
//memset: 将addr中所有字节用0替代并返回addr,作用是一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法
memset(&Socketaddr, 0, sizeof(Socketaddr));
Socketaddr.sin_len = sizeof(Socketaddr);
Socketaddr.sin_family = AF_INET;
Socketaddr.sin_port = htons(22222);//服务器绑定的端口号
Socketaddr.sin_addr.s_addr = inet_addr("服务器绑定的IP地址");
//将地址转化为CFDataRef
CFDataRef dataRef = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&Socketaddr, sizeof(Socketaddr));
//连接
//CFSocketError CFSocketConnectToAddress(CFSocketRef s, CFDataRef address, CFTimeInterval timeout);
//第一个参数 连接的socket
//第二个参数 连接的socket的包含的地址参数
//第三个参数 连接超时时间,如果为负,则不尝试连接,而是把连接放在后台进行,如果_socket消息类型为kCFSocketConnectCallBack,将会在连接成功或失败的时候在后台触发回调函数
CFSocketConnectToAddress(_socketRef, dataRef, -1);
//加入循环中
//获取当前线程的runLoop
CFRunLoopRef runloopRef = CFRunLoopGetCurrent();
//把socket包装成CFRunLoopSource, 最后一个参数是指有多个runloopsource通过一个runloop时候顺序,如果只有一个source 通常为0
CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socketRef, 0);
//加入运行循环
//第一个参数:运行循环管
//第二个参数: 增加的运行循环源, 它会被retain一次
//第三个参数:用什么模式把source加入到run loop里面,使用kCFRunLoopCommonModes可以监视所有通常模式添加source
CFRunLoopAddSource(runloopRef, sourceRef, kCFRunLoopCommonModes);
//之前被retain一次,这边要释放掉
CFRelease(sourceRef);
}
}
/**
回调函数
@param s socket对象
@param callbackType 这个socket对象的活动类型
@param address socket对象连接的远程地址,CFData对象对应的是socket对象中的protocol family (struct sockaddr_in 或者 struct sockaddr_in6), 除了type 类型 为kCFsocketAcceptCallBack 和kCFSocketDataCallBack ,否则这个值通常是NULL
@param data 跟回调类型相关的数据指针
kCFSocketConnectCallBack : 如果失败了, 它指向的就是SINT32的错误代码
kCFSocketAcceptCallBack : 它指向的就是CFSocketNativeHandle
kCFSocketDataCallBack : 它指向的就是将要进来的Data
其他情况就是NULL
@param info 与socket相关的自定义的任意数据
*/
void ServerConnectCallBack (CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info){
//判断是不是NULL
if (data != NULL){
printf("----->>>>>>连接失败\n");
}else{
printf("----->>>>>>连接成功\n");
}
}
//发送消息
- (void)sendMessage {
if (!_socketRef) {
[[[UIAlertView alloc] initWithTitle:@"对不起" message:@"请先连接服务器" delegate:self cancelButtonTitle:@"确定" otherButtonTitles: nil] show];
return;
}
NSString *stringTosend = @"sendMessage";
const char* data = [stringTosend UTF8String];
/** 成功则返回实际传送出去的字符数, 失败返回-1. 错误原因存于errno*/
long sendData = send(CFSocketGetNative(_socketRef), data, strlen(data) + 1, 0);
if (sendData < 0) {
perror("send");
}
}
//读取数据
- (void)_readStreamData{
//定义一个字符型变量
char buffer[512];
/*
int recv(SOCKET s, char FAR *buf, int len, int flags);
不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据
1. 第一个参数指定接收端套接字描述符
2.第二个参数指明一个缓冲区,改缓冲区用来存放recv函数接受到的数据
3. 第三个参数指明buf的长度
4.第四个参数一般置0
*/
long readData;
//若无错误发生,recv() 返回读入的字节数。如果连接已终止,返回0 如果发生错误,返回-1, 应用程序可通过perror() 获取相应错误信息
while ((readData = recv(CFSocketGetNative(_socketRef), buffer, sizeof(buffer), 0))) {
NSString *content = [[NSString alloc] initWithBytes:buffer length:readData encoding:NSUTF8StringEncoding];
NSLog(@"%@",content);
}
}
//清空socket
- (void)_releseSocket{
if (_socketRef) {
CFRelease(_socketRef);
}
_socketRef = NULL;
}
网友评论