Socket原理讲解
原文:https://blog.csdn.net/pashanhu6402/article/details/96428887
对TCP/IP、UDP、Socket编程这些词你不会很陌生吧?随着网络技术的发展,这些词充斥着我们的耳朵。那么我想问:
1. 什么是TCP/IP、UDP?
2. Socket在哪里呢?
3. Socket是什么呢?
4. 你会使用它们吗?
什么是TCP/IP、UDP?
TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。
UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。
这里有一张图,表明了这些协议的关系。
图1
TCP/IP协议族包括运输层、网络层、链路层。现在你知道TCP/IP与UDP的关系了吧。
Socket在哪里呢?
在图1中,我们没有看到Socket的影子,那么它到底在哪里呢?还是用图来说话,一目了然。

图2
原来Socket在这里。
Socket是什么呢?
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
你会使用它们吗?
前人已经给我们做了好多的事了,网络间的通信也就简单了许多,但毕竟还是有挺多工作要做的。以前听到Socket编程,觉得它是比较高深的编程知识,但是只要弄清Socket编程的工作原理,神秘的面纱也就揭开了。
一个生活中的场景。你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。 生活中的场景就解释了这工作原理,也许TCP/IP协议族就是诞生于生活中,这也不一定。
图3
先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
============================================
我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览器浏览网页 时,浏览器的进程怎么与web服务器通信的?当你用QQ聊天时,QQ进程怎么与服务器或你好友所在的QQ进程通信?这些都得靠socket?那什么是 socket?socket的类型有哪些?还有socket的基本函数,这些都是本文想介绍的。本文的主要内容如下:
-
1、网络中进程之间如何通信?
-
2、Socket是什么?
-
3、socket的基本操作
-
3.1、socket()函数
-
3.2、bind()函数
-
3.3、listen()、connect()函数
-
3.4、accept()函数
-
3.5、read()、write()函数等
-
3.6、close()函数
-
-
4、socket中TCP的三次握手建立连接详解
-
5、socket中TCP的四次握手释放连接详解
-
6、一个例子
IETF RFC33中发现的,撰写者为Stephen Carr、Steve Crocker和Vint Cerf。根据美国计算机历史博物馆的记载,Croker写道:“命名空间的元素都可称为套接字接口。一个套接字接口构成一个连接的一端,而一个连接可完全由一对套接字接口规定。”计算机历史博物馆补充道:“这比BSD的套接字接口定义早了大约12年。”
图1、socket中发送的TCP三次握手
从图中可以看出,当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。
总结:客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。
图2、socket中发送的TCP四次握手
图示过程如下:
-
某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;
-
另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;
-
一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;
-
接收到这个FIN的源发送端TCP对它进行确认。
这样每个方向上都有一个FIN和ACK。
view plaincopyprint?
- #include "InitSock.h"
- #include
- #include
- using namespace std;
- CInitSock initSock; // 初始化Winsock库
- int main()
- {
- // 创建套节字
- SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- //用来指定套接字使用的地址格式,通常使用AF_INET
- //指定套接字的类型,若是SOCK_DGRAM,则用的是udp不可靠传输
- //配合type参数使用,指定使用的协议类型(当指定套接字类型后,可以设置为0,因为默认为UDP或TCP)
- if(sListen == INVALID_SOCKET)
- {
- printf("Failed socket() \n");
- return 0;
- }
- // 填充sockaddr_in结构 ,是个结构体
- sockaddr_in sin;
- sin.sin_family = AF_INET;
- sin.sin_port = htons(4567); //1024 ~ 49151:普通用户注册的端口号
- sin.sin_addr.S_un.S_addr = INADDR_ANY;
- // 绑定这个套节字到一个本地地址
- if(::bind(sListen, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
- {
- printf("Failed bind() \n");
- return 0;
- }
- // 进入监听模式
- //2指的是,监听队列中允许保持的尚未处理的最大连接数
- if(::listen(sListen, 2) == SOCKET_ERROR)
- {
- printf("Failed listen() \n");
- return 0;
- }
- // 循环接受客户的连接请求
- sockaddr_in remoteAddr;
- int nAddrLen = sizeof(remoteAddr);
- SOCKET sClient = 0;
- char szText[] = " TCP Server Demo! \r\n";
- while(sClient==0)
- {
- // 接受一个新连接
- //((SOCKADDR*)&remoteAddr)一个指向sockaddr_in结构的指针,用于获取对方地址
- sClient = ::accept(sListen, (SOCKADDR*)&remoteAddr, &nAddrLen);
- if(sClient == INVALID_SOCKET)
- {
- printf("Failed accept()");
- }
- printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
- continue ;
- }
- while(TRUE)
- {
- // 向客户端发送数据
- gets(szText) ;
- ::send(sClient, szText, strlen(szText), 0);
- // 从客户端接收数据
- char buff[256] ;
- int nRecv = ::recv(sClient, buff, 256, 0);
- if(nRecv > 0)
- {
- buff[nRecv] = '\0';
- printf(" 接收到数据:%s\n", buff);
- }
- }
- // 关闭同客户端的连接
- ::closesocket(sClient);
- // 关闭监听套节字
- ::closesocket(sListen);
- return 0;
- }
客户端代码:
[cpp] view plaincopyprint?
- #include "InitSock.h"
- #include
- #include
- using namespace std;
- CInitSock initSock; // 初始化Winsock库
- int main()
- {
- // 创建套节字
- SOCKET s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- if(s == INVALID_SOCKET)
- {
- printf(" Failed socket() \n");
- return 0;
- }
- // 也可以在这里调用bind函数绑定一个本地地址
- // 否则系统将会自动安排
- // 填写远程地址信息
- sockaddr_in servAddr;
- servAddr.sin_family = AF_INET;
- servAddr.sin_port = htons(4567);
- // 注意,这里要填写服务器程序(TCPServer程序)所在机器的IP地址
- // 如果你的计算机没有联网,直接使用127.0.0.1即可
- servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
- if(::connect(s, (sockaddr*)&servAddr, sizeof(servAddr)) == -1)
- {
- printf(" Failed connect() \n");
- return 0;
- }
- char buff[256];
- char szText[256] ;
- while(TRUE)
- {
- //从服务器端接收数据
- int nRecv = ::recv(s, buff, 256, 0);
- if(nRecv > 0)
- {
- buff[nRecv] = '\0';
- printf("接收到数据:%s\n", buff);
- }
- // 向服务器端发送数据
- gets(szText) ;
- szText[255] = '\0';
- ::send(s, szText, strlen(szText), 0) ;
- }
- // 关闭套节字
- ::closesocket(s);
- return 0;
- }
封装的InitSock.h
[cpp] view plaincopyprint?
- #include
- #include
- #include
- #include
- #pragma comment(lib, "WS2_32") // 链接到WS2_32.lib
- class CInitSock
- {
- public:
- CInitSock(BYTE minorVer = 2, BYTE majorVer = 2)
- {
- // 初始化WS2_32.dll
- WSADATA wsaData;
- WORD sockVersion = MAKEWORD(minorVer, majorVer);
- if(::WSAStartup(sockVersion, &wsaData) != 0)
- {
- exit(0);
- }
- }
- ~CInitSock()
- {
- ::WSACleanup();
- }
- };