BSD Socket
创建Socket
调用socket(int addressFamily, int type, int protocol),返回值类型int
参数:
- addressFamily:Socket的网络域,IPV4(AF_INET )或者 IPV6(AF_INET6);
- type:Socket类型,流式Socket(SOCK_STREAM)、数据包Socket(SOCK_DGRAM)
- protocol:协议枚举值,根据Socket类型自动选择,流式选择IPPROTO_TCP,数据包则选择IPPROTO_UDP。
返回值:如果创建成功,返回值为新文件说明符的号码;如果创建失败,返回-1。
注意:创建完成后,通信尚未开始,Socket也没有被指定为输入或输出Socket(直到首次使用Socket时才会指定)。
建立连接
- 配置Socket服务器
(1)先调用bind(int socketFileDescriptor, sockaddr *addressToBind, int addressStructLength),与具有唯一地址的Socket关联。接收一个Socket并将其分配或绑定到某个特定的地址与端口。成功则返回0,否则返回-1.
(2)如果在socket(int, int, int)中的连接类型如果为UDP:可以开始向外界传输数据了,因为UDP是个无连接的协议,不需要再另一端监听;
(3)若为TCP:要调用listen(int socketFileDescriptor, int backlogSize)来建立好缓冲区队列的数据结构。socketFileDescriptor会成为只读socket,不能用于发送消息;backlogSize表示有多少个挂起的连接在排队的同时等待服务器代码的使用。在监听时,服务器会等待进来的连接请求并调用accept(int socketFileDescriptor, sockaddr *clientAddress, int clientAddressStructLength)来接收请求。这会将挂起的请求从缓冲区中移除,并使用客户端的地址信息(主要是IP和port)来装配clientAddress结构体。接受了挂起的请求后,服务器就可以从客户端接收消息了。 - Socket客户端连接
(1)TCP Socket:客户端首先通过connect(int socketFileDescriptor, sockaddr *serverAddress, int serverAddressStructLength)协商一个到服务器的连接。在TCP握手时该调用会阻塞,成功返回0,否则-1.
(2)UDP Socket:connect方法是可选的。如果调用它则会为所有的UDP传输Socket设定默认地址,这样会方便UDP数据包的发送和接收。如果设备通过主机名而不是IP地址进行连接,它可能不清楚如何继续,因为socketaddr结构体只包含一个IP地址。可以通过DNS(Domain Name System)将主机名转为IP地址。
发送/接收消息
- TCP
发送消息:int send(int socketFileDescriptor, char *buffer, int bufferLength, int flags), socketFileDescriptor:其描述的Socket会将缓存中介于0与bufferLength之间的字节发送出去。成功则返回成功发送出去的字节数量,失败返回-1。
接收消息:int receive(int socketFileDescriptor, char *buffer, int bufferLength, int flags),缓存会通过从Socket读取的第一个bufferLength长度的字节副本来装配。成功则返回成功读取的字节数量,失败返回-1。 - UDP:
(1)如果调用connect()来设定默认地址的UDP,则可以同上TCP一样调用send和receive;
(2)没有调用connect():
发送消息:int sendto(int socketFileDescriptor, char *buffer, int bufferLength, int flags, sockaddr *destinationAddress, int destinationAddressLength)使用相同的Socket连接发送给多个地址,和send类似,只不过它为目标地址提供额外的参数;
接收消息:int recvfrom(int socketFileDescriptor, char *buffer, int bufferLength, int flags, sockaddr *fromAddress, int *fromAddressLength),最后一个参数是指向整数的指针,值是fromAddress结构体的最终长度。
- 代码举例:创建socket来接收信息
- (void)loadCurrentStatus:(NSURL *)url {
// 创建流式Socket
int socketFileDescriptor = socket(AF_INET, SOCK_STREAM, 0);
if (socketFileDescriptor == -1) { // 创建失败
return;
}
// 将主机名转为IP
struct hostent *remoteHostEnt = gethostbyname([[url host] UTF8String]);
if (remoteHostEnt == NULL) {
// 转换失败
return;
}
struct in_addr *remoteAddr = (struct in_addr *)remoteHostEnt->h_addr_list[0];
// 设置socket参数来打开IP地址
struct sockaddr_in socketParameters;
socketParameters.sin_family = AF_INET;
socketParameters.sin_addr = *remoteAddr;
socketParameters.sin_port = htons([[url port] intValue]); // 整数转为网络字节序
// 连接socket
// 当sin_family为AF_INET时,sockaddr_in和sockaddr这两个结构体布局一样,所以sockaddr_in可以转为sockaddr
if (connect(socketFileDescriptor, (struct sockaddr * )&socketParameters, sizeof(socketParameters)) == -1) { // 连接失败
return;
}
// 连接成功
NSMutableData *data = [[NSMutableData alloc] init];
BOOL waitingForData = YES;
// 接收数据
while(waitingForData) {
const char *buffer[1024];
int length = sizeof(buffer);
// read a buffer's amount of data from the socket, the number of bytes read is returned.
int result = recv(socketFileDescriptor, &buffer, length, 0);
if (result > 0) {
// 接收成功
[data appendBytes:buffer length:result];
} else {
waitingForData = NO; // 退出接收数据
}
}
// 读取完成后关闭socket
close(socketFileDescriptor);
NSString *resultsString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"received string: %@", resultsString);
}
CFNetwork
对BSD Socket的一层轻量级封装,主要优势在于被集成到系统级的设置与主运行循环中。如必要时开启无效以及通过系统范围的VPN进行路由等,并且没有什么严重的缺陷。
创建socket:
CFStreamCreatePairWithSocketToHost(),可以针对给定的主机名和端口创建一对socket,一个用于读,一个用于写。其中框架会负责将主机名转换为IP地址,将端口号转换为网络字节序。如果不需要其中一个Socket,只需将NULL作为读或写流参数,就不会创建它了。
注意:使用前,必须通过CFReadStreamOpen()或CFWriteStreamOpen()打开流。这两个调用都是异步的,在成功打开后会通过kCFStreamEventOpenCompleted调用回调函数。
代码举例:创建与打开流
- (void)loadCurrentStatus:(NSURL *)url {
// keep a reference to self to use for controller callbacks
CFStreamClientContext ctx = {0, (__bridge void *)self, NULL, NULL, NULL};
// get callbacks for stream data, stream end, and any errors
CFOptionFlags registeredEvents = (kCFStreamEventHasBytesAvailable | // socket有可以读取的字节
kCFStreamEventEndEncountered | // socket到达字节流的末尾
kCFStreamEventErrorOccurred); // 操作出现错误
// 创建一个只读socket
CFReadStreamRef readStream;
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef)[url host], [[url port] intValue], &readStream, NULL);
//schedule the stream ton the run loop to enable callbacks
// 如果设置了kCFStreamEventOpenComplete,打开成功后会调用回调函数
// 注册socket回调函数
if (CFReadStreamSetClient(readStream, registeredEvents, socketCallback, &ctx)) {
// 根据给定的运行循环来调度流
CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
} else {
//调用回调失败
return;
}
// 打开readStream
if (CFReadStreamOpen(readStream) == NO) {
// 打开失败
return;
}
CFErrorRef error = CFReadStreamCopyError(readStream);
if (error != NULL) {
if (CFErrorGetCode(error) != 0) {
// 连接失败
}
CFRelease(error);
return;
}
//连接成功,去开始进程
CFRunLoopRun();
}
// 一个回调函数,当registeredEvents发生时,该函数就会被调用
void socketCallback(CFReadStreamRef stream, CFStreamEventType event, void *myPtr) {
switch (event) {
case kCFStreamEventHasBytesAvailable:
// 读取bytes
while(CFReadStreamHasBytesAvailable(stream)) {
UInt8 buffer[1024];
int numBytesRead = CFReadStreamRead(stream, buffer, 1024);
NSData *data = [NSData dataWithBytes:buffer length:numBytesRead];
NSLog(@"接收到的数据 = %@", data);
}
break;
case kCFStreamEventErrorOccurred: {
CFErrorRef error = CFReadStreamCopyError(stream);
if (error != NULL) {
if (CFErrorGetCode(error) != 0) {
// 获取错误信息
}
CFRelease(error);
}
}
break;
case kCFStreamEventEndEncountered: {
// 关闭stream
CFReadStreamClose(stream);
// stop processing callback methods
CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
// 结束当前线程的主运行
CFRunLoopStop(CFRunLoopGetCurrent());
}
break;
default:
break;
}
}