Socket通信

一、网络各个协议:TCP/IP、SOCKET、HTTP等

网络七层由下往上分别为物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。

其中物理层、数据链路层和网络层通常被称作媒体层,是网络工程师所研究的对象;

传输层、会话层、表示层和应用层则被称作主机层,是用户所面向和关心的内容。

 http协议   对应于应用层 

 tcp协议    对应于传输层  

 ip协议     对应于网络层 

 三者本质上没有可比性。  何况HTTP协议是基于TCP连接的。 

 TCP/IP是传输层协议,主要解决数据如何在网络中传输;而HTTP是应用层协议,主要解决如何包装数据。

 我们在传输数据时,可以只使用传输层(TCP/IP),但是那样的话,由于没有应用层,便无法识别数据内容,如果想要使传输的数据有意义,则必须使用应用层协议,应用层协议很多,有HTTP、FTP、TELNET等等,也可以自己定义应用层协议。WEB使用HTTP作传输层协议,以封装HTTP文本信息,然后使用TCP/IP做传输层协议将它发送到网络上。Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。

二、Http和Socket连接区别

 相信不少初学手机联网开发的朋友都想知道Http与Socket连接究竟有什么区别,希望通过自己的浅显理解能对初学者有所帮助。

2.1、TCP连接

要想明白Socket连接,先要明白TCP连接。手机能够使用联网功能是因为手机底层实现了TCP/IP协议,可以使手机终端通过无线网络建立TCP连接。TCP协议可以对上层网络提供接口,使上层网络数据的传输建立在“无差别”的网络之上。

建立起一个TCP连接需要经过“三次握手”:

第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过“四次握手”(过程就不细写了,就是服务器和客户端交互,最终确定断开)

2.2、HTTP连接

HTTP协议即超文本传送协议(HypertextTransfer Protocol ),是Web联网的基础,也是手机联网常用的协议之一,HTTP协议是建立在TCP协议之上的一种应用。

HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。

1)在HTTP 1.0中,客户端的每次请求都要求建立一次单独的连接,在处理完本次请求后,就自动释放连接。

2)在HTTP 1.1中则可以在一次连接中处理多个请求,并且多个请求可以重叠进行,不需要等待一个请求结束后再发送下一个请求。

由于HTTP在每次请求结束后都会主动释放连接,因此HTTP连接是一种“短连接”,要保持客户端程序的在线状态,需要不断地向服务器发起连接请求。通常的做法是即时不需要获得任何数据,客户端也保持每隔一段固定的时间向服务器发送一次“保持连接”的请求,服务器在收到该请求后对客户端进行回复,表明知道客户端“在线”。若服务器长时间无法收到客户端的请求,则认为客户端“下线”,若客户端长时间无法收到服务器的回复,则认为网络已经断开。

三、SOCKET原理

3.1、套接字(socket)概念

套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。

应用层通过传输层进行数据通信时,TCP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个 TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了套接字(Socket)接口。应用层可以和传输层通过Socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。

3.2 、建立socket连接

建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket,另一个运行于服务器端,称为ServerSocket。

套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。

服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。

客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

 3.3、SOCKET连接与TCP连接

创建Socket连接时,可以指定使用的传输层协议,Socket可以支持不同的传输层协议(TCP或UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接。

 3.4、Socket连接与HTTP连接

由于通常情况下Socket连接就是TCP连接,因此Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开。但在实际网络应用中,客户端到服务器之间的通信往往需要穿越多个中间节点,例如路由器、网关、防火墙等,大部分防火墙默认会关闭长时间处于非活跃状态的连接而导致 Socket 连接断连,因此需要通过轮询告诉网络,该连接处于活跃状态。

而HTTP连接使用的是“请求—响应”的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据。

很多情况下,需要服务器端主动向客户端推送数据,保持客户端与服务器数据的实时与同步。此时若双方建立的是Socket连接,服务器就可以直接将数据传送给客户端;若双方建立的是HTTP连接,则服务器需要等到客户端发送一次请求后才能将数据传回给客户端,因此,客户端定时向服务器端发送连接请求,不仅可以保持在线,同时也是在“询问”服务器是否有新的数据,如果有就将数据传给客户端。

客户端:

导入头文件:

#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#import <unistd.h>

1. 创建连接

 1 CFSocketContext sockContext = {0,         // 结构体的版本,必须为0
 2 self,                          // 一个任意指针的数据,可以用在创建时CFSocket对象相关联。这个指针被传递给所有的上下文中定义的回调。
 3 NULL,                         // 一个定义在上面指针中的retain的回调, 可以为NULL
 4 NULL, NULL};
 5 
 6 CFSocketRef _socket = (kCFAllocatorDefault,    // 为新对象分配内存,可以为nil
 7 PF_INET,                            // 协议族,如果为0或者负数,则默认为PF_INET
 8 SOCK_STREAM,                                   // 套接字类型,如果协议族为PF_INET,则它会默认为SOCK_STREAM
 9 IPPROTO_TCP,                                   // 套接字协议,如果协议族是PF_INET且协议是0或者负数,它会默认为IPPROTO_TCP
10 kCFSocketConnectCallBack,               // 触发回调函数的socket消息类型,具体见Callback Types
11 TCPServerConnectCallBack,              // 上面情况下触发的回调函数
12 &sockContext                     // 一个持有CFSocket结构信息的对象,可以为nil
13 );
14 
15 if (_socket != nil) {
16     struct sockaddr_in addr4;   // IPV4
17     memset(&addr4, 0, sizeof(addr4));
18     addr4.sin_len = sizeof(addr4);
19     addr4.sin_family = AF_INET;
20     addr4.sin_port = htons(8888);
21     addr4.sin_addr.s_addr = inet_addr([strAddress UTF8String]);  // 把字符串的地址转换为机器可识别的网络地址
22        
23     // 把sockaddr_in结构体中的地址转换为Data
24     CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&addr4, sizeof(addr4));
25     CFSocketConnectToAddress(_socket,          // 连接的socket
26 address,                            // CFDataRef类型的包含上面socket的远程地址的对象
27 -1  // 连接超时时间,如果为负,则不尝试连接,而是把连接放在后台进行,如果_socket消息类型为kCFSocketConnectCallBack,将会在连接成功或失败的时候在后台触发回调函数
28 );
29 
30     CFRunLoopRef cRunRef = CFRunLoopGetCurrent();    // 获取当前线程的循环
31     // 创建一个循环,但并没有真正加如到循环中,需要调用CFRunLoopAddSource
32     CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socket, 0);
33     CFRunLoopAddSource(cRunRef,              // 运行循环
34     sourceRef,                        // 增加的运行循环源, 它会被retain一次
35     kCFRunLoopCommonModes                 // 增加的运行循环源的模式
36     );
37     CFRelease(courceRef);
38 }

2. 设置回调函数

 1 // socket回调函数的格式:
 2 static void TCPServerConnectCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) {
 3     if (data != NULL) {
 4         // 当socket为kCFSocketConnectCallBack时,失败时回调失败会返回一个错误代码指针,其他情况返回NULL
 5         NSLog(@"连接失败");
 6         return;
 7     }
 8     TCPClient *client = (TCPClient *)info;
 9     // 读取接收的数据
10     [info performSlectorInBackground:@selector(readStream) withObject:nil];
11

3. 接收发送数据

 1 // 读取接收的数据
 2 - (void)readStream {
 3     char buffer[1024];
 4     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 5     while (recv(CFSocketGetNative(_socket), //与本机关联的Socket 如果已经失效返回-1:INVALID_SOCKET
 6            buffer, sizeof(buffer), 0)) {
 7         NSLog(@"%@", [NSString stringWithUTF8String:buffer]);
 8     }
 9 }
10 
11 // 发送数据
12 - (void)sendMessage {
13     NSString *stringTosend = @"你好";
14     char *data = [stringTosend UTF8String];
15     send(SFSocketGetNative(_socket), data, strlen(data) + 1, 0);
16 }

服务器端:

  1 CFSockteRef _socket;
  2 CFWriteStreamRef outputStream = NULL;
  3 
  4 int setupSocket() {
  5     _socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, TCPServerAcceptCallBack, NULL);
  6     if (NULL == _socket) {
  7         NSLog(@"Cannot create socket!");
  8         return 0;
  9     }
 10    
 11     int optval = 1;
 12     setsockopt(CFSocketGetNative(_socket), SOL_SOCKET, SO_REUSEADDR, // 允许重用本地地址和端口
 13 (void *)&optval, sizeof(optval));
 14    
 15     struct sockaddr_in addr4;
 16     memset(&addr4, 0, sizeof(addr4));
 17     addr4.sin_len = sizeof(addr4);
 18     addr4.sin_family = AF_INET;
 19     addr4.sin_port = htons(port);
 20     addr4.sin_addr.s_addr = htonl(INADDR_ANY);
 21     CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&addr4, sizeof(addr4));
 22    
 23     if (kCFSocketSuccess != CFSocketSetAddress(_socket, address)) {
 24         NSLog(@"Bind to address failed!");
 25         if (_socket)
 26              CFRelease(_socket);
 27         _socket = NULL;
 28         return 0;
 29     }
 30        
 31     CFRunLoopRef cfRunLoop = CFRunLoopGetCurrent();
 32     CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socket, 0);
 33     CFRunLoopAddSource(cfRunLoop, source, kCFRunLoopCommonModes);
 34     CFRelease(source);
 35    
 36     return 1;
 37 }
 38 
 39 // socket回调函数,同客户端
 40 void TCPServerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) {
 41     if (kCFSocketAcceptCallBack == type) {
 42         // 本地套接字句柄
 43         CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
 44         uint8_t name[SOCK_MAXADDRLEN];      
 45         socklen_t nameLen = sizeof(name);
 46         if (0 != getpeername(nativeSocketHandle, (struct sockaddr *)name, &nameLen)) {
 47             NSLog(@"error");
 48             exit(1);
 49         }
 50         NSLog(@"%@ connected.", inet_ntoa( ((struct sockaddr_in *)name)->sin_addr )):
 51 
 52         CFReadStreamRef iStream;
 53         CFWriteStreamRef oStream;
 54         // 创建一个可读写的socket连接
 55         CFStreamCreatePairWithSocket(kCFAllocatorDefault, nativeSocketHandle, &iStream, &oStream); 
 56         if (iStream && oStream) {
 57             CFStreamClientContext streamContext = {0, NULL, NULL, NULL};
 58             if (!CFReadStreamSetClient(iStream, kCFStreamEventHasBytesAvaiable,
 59                                        readStream, // 回调函数,当有可读的数据时调用
 60                                        &streamContext)){
 61                 exit(1);
 62             }
 63 
 64             if (!CFReadStreamSetClient(iStream, kCFStreamEventCanAcceptBytes, writeStream, &streamContext)){
 65                 exit(1);
 66             }
 67 
 68             CFReadStreamScheduleWithRunLoop(iStream, CFRunLoopGetCurrent(), kCFRunLoopCommomModes);
 69             CFWriteStreamScheduleWithRunLoop(wStream, CFRunLoopGetCurrent(), kCFRunLoopCommomModes);
 70             CFReadStreamOpen(iStream);
 71             CFWriteStreamOpen(wStream);
 72         } else {
 73              close(nativeSocketHandle); 
 74         }
 75     }
 76 }
 77 
 78 // 读取数据
 79 void readStream(CFReadStreamRef stream, CFStreamEventType eventType, void *clientCallBackInfo) {
 80     UInt8 buff[255];
 81     CFReadStreamRead(stream, buff, 255);
 82     printf("received: %s", buff);
 83 }
 84 
 85 void writeStream (CFWriteStreamRef stream, CFStreamEventType eventType, void *clientCallBackInfo) {
 86     outputStream = stream;
 87 }
 88 
 89 main {
 90     char *str = "nihao";
 91    
 92     if (outputStream != NULL) {
 93         CFWriteStreamWrite(outputStream, str, strlen(line) + 1);
 94     } else {
 95         NSLog(@"Cannot send data!");
 96     }
 97 }
 98 
 99 // 开辟一个线程线程函数中
100 void runLoopInThread() {
101     int res = setupSocket();
102     if (!res) {
103         exit(1);
104     }
105     CFRunLoopRun();    // 运行当前线程的CFRunLoop对象
106 } 
posted @ 2014-07-25 11:42  激情为梦想而生  阅读(919)  评论(0编辑  收藏  举报