Socket
-
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。
- 网络中进程之间如何通信
- 网络中进程之间如何通信?首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。其实TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。
- 使用TCP/IP协议的应用程序通常采用应用编程接口:UNIX BSD的套接字(socket)和UNIX System V的TLI(已经被淘汰),来实现网络进程之间的通信。就目前而言,几乎所有的应用程序都是采用socket,而现在又是网络时代,网络中进程通信是无处不在,这就是我为什么说“一切皆socket”。
- socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。我的理解就是Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)
关于如下问题可参考这里
网络中进程之间如何通信?
socket的基本操作
socket()函数
bind()函数
listen()、connect()函数
accept()函数
read()、write()函数等
close()函数
iOS网络编程层次模型
- Cocoa层:NSURL,Bonjour,Game Kit,WebKit
- Core Foundation层:基于 C 的 CFNetwork 和 CFNetServices
- OS层:基于 C 的 BSD socket
Cocoa层:是最上层的基于 Objective-C 的 API,比如 URL访问,NSStream,Bonjour,GameKit等,这是大多数情况下我们常用的 API。Cocoa 层是基于 Core Foundation 实现的。
Core Foundation层:因为直接使用 socket 需要更多的编程工作,所以苹果对 OS 层的 socket 进行简单的封装以简化编程任务。该层提供了 CFNetwork 和 CFNetServices,其中 CFNetwork 又是基于 CFStream 和 CFSocket。
OS层:最底层的 BSD socket 提供了对网络编程最大程度的控制,但是编程工作也是最多的。因此,苹果建议我们使用 Core Foundation 及以上层的 API 进行编程。
C/S架构程序设计基本框架
常用的Socket类型
有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。
- TCP C/S架构程序设计基本框架
- UDP C/S架构程序设计基本框架
SocketDemo
主要使用:NSStream、CFStream、CFSocket
效果图
客户端主要代码、完整代码下载地址
#include
#include
#include
static ViewController *selfClass =nil;
@interface ViewController ()
{
NSInteger flag;
}
@property (nonatomic, strong) NSInputStream *inputStream;
@property (nonatomic, strong) NSOutputStream *outputStream;
@property (nonatomic, assign) NSInteger kPort;
@property (weak, nonatomic) IBOutlet UITextField *port;
@property (weak, nonatomic) IBOutlet UITextField *serviceIP;
@property (weak, nonatomic) IBOutlet UILabel *message;
@property (weak, nonatomic) IBOutlet UITextField *putText;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
selfClass = self;
// Do any additional setup after loading the view, typically from a nib.
}
- (IBAction)connectService:(id)sender {
flag = 0;
[self initNetworkCommunication];
}
- (IBAction)reviveMsg:(id)sender {
flag = 1;
[self initNetworkCommunication];
}
- (void)initNetworkCommunication {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)self.serviceIP.text, (int)[self.port.text intValue], &readStream, &writeStream);
_inputStream = (__bridge_transfer NSInputStream *)readStream;
_outputStream = (__bridge_transfer NSOutputStream *)writeStream;
[_inputStream setDelegate:self];
[_outputStream setDelegate:self];
[_inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[_inputStream open];
[_outputStream open];
}
- (void)close {
[_outputStream close];
[_outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[_outputStream setDelegate:nil];
[_inputStream close];
[_inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[_inputStream setDelegate:nil];
}
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
NSString *event;
switch (eventCode) {
case NSStreamEventNone:
event = @"NSStreamEventNone";
NSLog(@"%@",event);
break;
case NSStreamEventOpenCompleted:
event = @"NSStreamEventOpenCompleted";
NSLog(@"%@",event);
break;
case NSStreamEventHasBytesAvailable:
event = @"NSStreamEventHasBytesAvailable";
NSLog(@"%@",event);
if (flag == 1 && aStream == _inputStream) {
NSMutableData *input = [[NSMutableData alloc] init];
uint8_t buffer[1024];
NSInteger len;
while ([_inputStream hasBytesAvailable]) {
len = [_inputStream read:buffer maxLength:sizeof(buffer)];
if (len > 0) {
[input appendBytes:buffer length:len];
}
}
NSString *result = [[NSString alloc] initWithData:input encoding:NSUTF8StringEncoding];
NSLog(@"%@",result);
_message.text = result;
}
case NSStreamEventHasSpaceAvailable:
event = @"NSStreamEventHasSpaceAvailable";
NSLog(@"%@",event);
if (flag ==0 && aStream == _outputStream) {
//输出
NSString * putText = selfClass.putText.text;
UInt8 buff[1024];
memcpy(buff, [putText UTF8String],[putText lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1);
[_outputStream write:buff maxLength: strlen((const char*)buff)+1];
//必须关闭输出流否则,服务器端一直读取不会停止,
[_outputStream close];
}
break;
case NSStreamEventErrorOccurred:
event = @"NSStreamEventErrorOccurred";
NSLog(@"%@",event);
[self close];
break;
case NSStreamEventEndEncountered:
event = @"NSStreamEventEndEncountered";
NSLog(@"%@",event);
NSLog(@"Error:%ld:%@",[[aStream streamError] code], [[aStream streamError] localizedDescription]);
break;
default:
[self close];
event = @"Unknown";
break;
}
}
服务端主要代码、完整代码下载地址
#import "ViewController.h"
#import
#include
#include
static ViewController *selfClass =nil;
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *portText;
@property (nonatomic, assign) NSInteger kPORT;
@property (weak, nonatomic) IBOutlet UITextField *putText;
@property (weak, nonatomic) IBOutlet UILabel *readText;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
selfClass = self;
// Do any additional setup after loading the view, typically from a nib.
}
- (IBAction)startService:(id)sender {
//设置服务端端口号
self.kPORT = [self.portText.text integerValue];
CFSocketRef service;
/*CFSocketContext 参数cgindex version 版本号,必须为0;
*void *info; 一个指向任意程序定义数据的指针,可以在CFScocket对象刚创建的时候与之关联,被传递给所有在上下文中回调,可为NULL;
*CFAllocatorRetainCallBack retain; info指针中的retain回调,可以为NULL
*CFAllocatorReleaseCallBack release; info指针中的release的回调,可以为NULL
*CFAllocatorCopyDescriptionCallBack copyDescription; info指针中的回调描述,可以为NULL
*/
CFSocketContext CTX = {0,NULL,NULL,NULL,NULL};
//CFSockerRef
//内存分配类型,一般为默认的Allocator->kCFAllocatorDefault,
//协议族,一般为Ipv4:PF_INET,(Ipv6,PF_INET6),
//套接字类型,TCP用流式—>SOCK_STREAM,UDP用报文式->SOCK_DGRAM,
//套接字协议,如果之前用的是流式套接字类型:IPROTO_TCP,如果是报文式:IPPROTO_UDP,
//回调事件触发类型 *1,
//触发时候调用的方法 *2,
//用户定义的数据指针,用于对CFSocket对象的额外定义或者申明,可以为NULL
service = CFSocketCreate(NULL, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, (CFSocketCallBack)AcceptCallBack, &CTX);
if (service == NULL) {
return;
}
//设置是否重新绑定标志
int yes = 1;
/* 设置socket属性 SOL_SOCKET是设置tcp SO_REUSEADDR是重新绑定,yes 是否重新绑定*/
setsockopt(CFSocketGetNative(service), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
//设置端口和地址
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr)); //memset函数对指定的地址进行内存拷贝
addr.sin_len = sizeof(addr);
addr.sin_family = AF_INET; //AF_INET是设置 IPv4
addr.sin_port = htons(self.kPORT); //htons函数 无符号短整型数转换成“网络字节序”
addr.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY有内核分配,htonl函数 无符号长整型数转换成“网络字节序”
/* 从指定字节缓冲区复制,一个不可变的CFData对象*/
CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&addr, sizeof(addr));
/* 设置Socket*/
if (CFSocketSetAddress(service, (CFDataRef)address) != kCFSocketSuccess) {
fprintf(stderr, "Socket绑定失败\n");
CFRelease(service);
return ;
}
/* 创建一个Run Loop Socket源 */
CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, service, 0);
/* Socket源添加到Run Loop中 */
CFRunLoopAddSource(CFRunLoopGetCurrent(), sourceRef, kCFRunLoopCommonModes);
CFRelease(sourceRef);
printf("Socket listening on port %zd\n", self.kPORT);
/* 运行Loop */
CFRunLoopRun();
}
//接受客户端请求后回调函数
void AcceptCallBack(
CFSocketRef socket,
CFSocketCallBackType type,
CFDataRef address,
const void *data,
void *info)
{
CFReadStreamRef readStream = NULL;
CFWriteStreamRef writeStream = NULL;
//data参数的含义是,如果是kCFSocketAcceptCallBack类型,data是CFSocketNativeHandle类型的指针
CFSocketNativeHandle sock = *(CFSocketNativeHandle *) data;
//创建读写socket流
CFStreamCreatePairWithSocket(kCFAllocatorDefault, sock, &readStream, &writeStream);
if (!readStream || !writeStream) {
close(sock);
fprintf(stderr, "CFStreamCreatePairWithSocket() 失败\n");
return;
}
CFStreamClientContext streamCtxt = {0,NULL,NULL,NULL,NULL};
//注册两种回调函数
CFReadStreamSetClient(readStream, kCFStreamEventHasBytesAvailable, ReadStreamClientCallBack, &streamCtxt);
CFWriteStreamSetClient(writeStream, kCFStreamEventCanAcceptBytes, WriteStreamClientCallBack, &streamCtxt);
//加入到循环当中
CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(),kCFRunLoopCommonModes);
CFWriteStreamScheduleWithRunLoop(writeStream, CFRunLoopGetCurrent(),kCFRunLoopCommonModes);
CFReadStreamOpen(readStream);
CFWriteStreamOpen(writeStream);
}
//读取操作,读取客户端发送的数据
static UInt8 buff[255];
void ReadStreamClientCallBack (CFReadStreamRef stream,CFStreamEventType eventType, void* clientCallBackInfo ) {
CFReadStreamRef inputStream = stream;
if (NULL != inputStream) {
CFReadStreamRead(inputStream, buff, 255);
printf("接收到的数据 :%s\n", buff);
selfClass.readText.text = [NSString stringWithCString:(char*)buff encoding:NSUTF8StringEncoding];
CFReadStreamClose(inputStream);
//从循环中移除
CFReadStreamUnscheduleFromRunLoop(inputStream, CFRunLoopGetCurrent(),kCFRunLoopCommonModes);
inputStream = NULL;
}
}
/* 写入流操作 客户端在读取数据时候调用 */
void WriteStreamClientCallBack(CFWriteStreamRef stream, CFStreamEventType eventType, void* clientCallBackInfo)
{
CFWriteStreamRef outputStream = stream;
//输出
NSString * putText = selfClass.putText.text;
UInt8 buff[1024];
memcpy(buff, [putText UTF8String],[putText lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1);
if(NULL != outputStream)
{
CFWriteStreamWrite(outputStream, buff, strlen((const char*)buff)+1);
//关闭输出流
CFWriteStreamClose(outputStream);
//从循环中移除
CFWriteStreamUnscheduleFromRunLoop(outputStream, CFRunLoopGetCurrent(),kCFRunLoopCommonModes);
outputStream = NULL;
}
}
写在最后
- 参考
Linux Socket编程(不限Linux) - 第三方库
CocoaAsyncSocket
客户端完整代码下载地址
服务端完整代码下载地址