简单理解和实现socket(套接字)
学习总结
1、什么是套接字?
套接字(socket)是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要 通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字 (Socket)的接口,区分不同应用程序进程间的网络通信和连接。
生成套接字,主要有3个参数:通信的目的IP地址、使用的传输 层协议(TCP或UDP)和使用的端口号。Socket原意是“插座”。通过将这3个参数结合起来,与一个“插座”Socket绑定,应用层就可以和传输 层通过套接字接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。
socket相当于用电话机与他人通话,首先要做的就是安装电话机。
而安装电话机需要用到以下步骤来完成:
第一步 安装电话机 创建socket
//代码示例:
//创建socket需要调用 Winsock2.h 头文件
SOCKET sockCli = socket
(
//第一个参数代表地址族规范
AF_INET,//表示IPv4地址族
//第二个参数代表socket的类型
SOCK_STREAM,// 面向连接 TCP协议 一般用于 IPv4 IPv6 地址族
//第三个参数 要使用的协议
0 //为0 表示调用者不希望指定协议,服务提供商将选择要使用的协议
);
第二步 安装完电话机接下来要干什么?
当然是分配电话号码
//SOCKADDR_IN 指定AF_INET地址族的传输地址和端口
SOCKADDR_IN addrSrv;
//设置指定IPv4传输地址
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//htonl 地址转化 INADDR_ANY 任何地址
//传输地址的地址族
addrSrv.sin_family = AF_INET;
//传输协议端口号
//当服务端与客户段端口号不同是将无法连接成功
addrSrv.sin_port = htons(6000);
//分配电话号码
bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR) );
第三步 监听
//监听传入连接状态的电话机
listen(sockSrv, 5)//最大的监听数目 ,执行到listen,但尚未执行到accept
第四步 分配一台分机去处理客户端的端连接
SOCKADDR_IN addrCli;
int len = sizeof(SOCKADDR);
//分配一台分机
SOCKET sockConn = accept //由服务器分配
(
sockSrv,
(SOCKADDR*)&addrCli,
&len
);
//拿起电话机,准备接听
char sendBuf[100] = { 0 };
sprintf_s(
sendBuf,//数据地址
100,
"Welcome %s to China!",
inet_ntoa(addrCli.sin_addr)//成员IP地址
);
第五步 开始通话 发收数据
//发送数据
int iLen = send(
sockConn,
sendBuf,
strlen(sendBuf),
0
);
//接受数据
char recvBuf[100] = { 0 };
iLen = recv(sockConn, recvBuf, 100, 0);
printf("recvBuf=%s\n", recvBuf);//打印数据
第六步 服务完毕 关闭分机
closesocket(sockConn);
第七步 全部服务结束 关闭总机
closesocket(sockSrv);
以上就是一个简单的服务器配置流程 有了服务端自然需要客户端。
客户端的配置流程如下:
第一步 依旧是安装电话机
//与服务端写法相同
SOCKET sockCli = socket
(
AF_INET,
SOCK_STREAM,//面向连接 TCP
0
);
第二步 配置要连接的服务器
SOCKADDR_IN addrSrv;
//服务端地址使用本机地址即可
//具体如何查看本机IP地址请百度搜索
addrSrv.sin_addr.S_un.S_addr = inet_addr("114.114.114.114");
//地址族
addrSrv.sin_family = AF_INET;
//端口
addrSrv.sin_port = htons(6000);
第三步 连接服务端
connect(sockCli, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR))
第四部 收发数据
//收取数据
char recvBuf[100] = { 0 };
int iLen = recv(sockCli, recvBuf, 100, 0);
printf("recvBuf = %s\n", recvBuf);
//发送数据
char sendBuf[100] = "hello word!";
iLen = send(sockCli, (char*)sendBuf, 100, 0);
第五步 关闭套接字
//5 关闭套接字
closesocket(sockCli);
WSACleanup();
以上就是客户端和服务端的一个简单的小dome,如果大家按我上面的代码尝试的话会碰到可能碰到以下几个错误
第一个错误 服务器与客户端无法连接
此时可以使用 INVALID_SOCKET 来调试
if (INVALID_SOCKET == sockSrv)
{
printf("socket errorno = %d\n", GetLastError());
return -1;
}
运行调试后返回错误号 10093,若读者使用的是VS2019可以在工具栏中找到
错误查找 来查询错误
这个错误的大概原因就是为初始化网络库
具体代码可参考以下示例或者网络搜索:
WORD wVersionRequested;
WSADATA wasData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wasData);
if (err != 0)
{
printf("WSAStartup errorNum = %d\n", GetLastError());
return err;
}
if (LOBYTE(wasData.wVersion) != 2 || HIBYTE(wasData.wVersion) != 2)
{
printf("LOBYTE orrorNum = %d\n", GetLastError());
WSACleanup();
return -1;
}
第二个错误 IP地址或端口错误 错误码 10060
第三个错误 点击生成时报错 提示 _WINSOCK_DEPRECATED_NO_WARNINGS
具体解决方法请百度直接搜索
以上就是套接字(socket)的解释与一个简单的实现。
2、什么是IP地址和端口,为什么有些端口不能用?
一、什么是IP地址和端口
1、什么是IP地址
IP地址就是网络中用来标记地址的一组数字,就像门牌号一样。例如你想要去你朋友加你就需要知道你朋友家的门牌号是多少。
2、什么是端口
端口可以认为是设备与外界通讯交流的出口。端口可分为虚拟端口和物理端口,其中虚拟端口指计算机内部或交换机路由器内的端口,物理端口又称为接口,是可见端口,计算机背板的RJ45网口,交换机路由器集线器等RJ45端口。
二、为什么有些端口不能用?
代理服务器常用以下端口:
(1). HTTP协议代理服务器常用端口号:80/8080/3128/8081/9080
(2). SOCKS代理协议服务器常用端口号:1080
(3). FTP(文件传输)协议代理服务器常用端口号:21
(4). Telnet(远程登录)协议代理服务器常用端口:23
8080端口说明:
8080端口同80端口,是被用于WWW代理服务的,可以实现网页浏览
3、TCP客户端和服务端如何通信?
一、服务器初始化
1)调用 socket,创建文件描述符。
2)调用bind,将文件描述符与IP/port连接起来。若端口号已被占用则会bind失败。
3)调用listen,声明该文件描述符是服务器的一个文件描述符,为之后的accept做准备。
4)调用accept,并处于阻塞状态,等待客户端连接
二、建立连接
1)调用socket,创建文件描述符。
2)调用connect,向服务器发起连接请求。
3)connect会发送一个请求SYN段并阻塞等待服务器应答(第一次握手)。
4)服务器收到SYN,会给客户端发送一个确认应答的同时发送一个请求(SYN+ACK),表示同意建立连接(第二次握手)。
5)客户端收到客户端发的SYN+ACK段,表明客户端连接已建立成功,进入ESTABLISHED状态,从connect()。客户端再向服务器发送一个ACK段,服务器收到后则服务器端连接也建立成功,服务器也进入ESTABLISHED状态。
三、数据传输
1)连接建立成功后,在同一连接、同一时刻、通信双方可同时写数据(全双工)。
2)服务端从 accept() 返回后调用 read() 开始读取数据,若没有数据则阻塞等待。
3)客户端调用 write() 向服务端发送数据请求,客户端收到之后调用 read() 处理请求,此过程服务端调用 read() 阻塞等待。
4)服务端调用 write() 将处理好的请求发送给客户端,再次调用 read()等待下一次请求。
5)客户端收到后 read() 返回,发送下一条请求,如此循环下去。
四、断开连接
1)没有数据处理了,则客户端调用close() 关闭连接,给服务端发送一个断开连接请求FIN段(第一次握手)。
2)服务器收到客户端的FIN段,给客户端发送一个确认应答ACK段,表明同意断开连接。客户端收到ACK段并调用read() 返回,表明客户端连接已断开(第二次握手)。
3)read()返回 0后,服务端知道客户端已经断开连接,它也调用close()关闭连接,给客户端发送一个断开连接请求FIN段(第三次握手)。
4)客户端收到服务端发送的FIN段,就给服务端一个确认应答ACK段,表明同意断开连接,客户端进入TIME_WAIT状态,服务端收到客户端的ACK段后也断开连接。
推荐阅读
-
Python实现同时兼容老版和新版Socket协议的一个简单WebSocket服务器
-
Java中多态到底怎么用和怎么实现(最简单的理解)
-
Python网络编程——socket套接字实现UDP/TCP信息传输
-
Java Socket(套接字)编程、TCP和UDP通信过程详解
-
网络套接字socket,利用UDP协议实现服务器与客户端通信
-
python实现 socket套接字---UDP&TCP
-
linux网络编程高级篇-原始套接字【简单的抓包实现】
-
socket实现简单的服务器端和客户端的交互
-
linux进程间通信---本地socket套接字(五)---多路IO转接服务器实现一个server对应多个client---poll实现
-
Python实现同时兼容老版和新版Socket协议的一个简单WebSocket服务器