目录

为什么socket编程又叫套接字编程?

TCP服务端

初始化套接字库——WSAStarup

创建套接字——socket

绑定到本机 ——bind

开始监听 ——listen

连接客户端请求——accept

发送与接收数据 ——send/recv

完整代码

TCP客户端

初始化套接字库——WSAStarup

创建套接字

连接服务器——connect

发送与接收数据

完整代码


为什么socket编程又叫套接字编程?

为什么要称socket为套接字?首先套接字的原词为"socket",直译过来就是插座的意思,最先采用这个词的人,觉得网络连接,就像插口和插座一样,一方插,一方被插(知乎用户回答)

除此之外,linux等系统中“套接字”对应“socket word”,所以“字”也就是对应“word”,可能指计算机数据,也可能指存储socket的数据表示,因为端口号是两个字节,就是一个WORD。

至于为什么翻译为“套接字”:有人说是“套用-接口-标识”的意思;有人说是“套接起来的字符串”的意思;有人说“是将网络数据包一层一层地套起来传输”的意思。

总之就是望文生义,毕竟怎么解释都不重要,东西还是那个东西

看了一圈,最比较赞同的解释是:套接指的是套接管,就是奖两根水管套接起来的关资,然后“字”就是连接数据的标识符,所以套接字就是标识连接的数据体。(附作者链接Socket为什么要翻译成套接字? - FrankIsFree的回答 - 知乎

TCP服务端

首先创建一个空的控制台应用,按照TCP实现流程编写代码。

初始化套接字库——WSAStarup

//初始化套接字库
WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);  //MAKEWORD(a,b) b|a<<8
err = WSAStartup(wVersion, &wsaData);
//检查1
if (err != 0) {return err;
}
//检查2
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {//清理套接字库WSACleanup();return -1;
}

当我们再进行socket编程时,要调用各种socket函数,而且还需要用到一个库文件Ws2_32.lib个一个头文件Winsock2.h

wsastartup()函数向操作系统说明,我们要用哪个库文件。 因此就可以将库文件与当前的应用程序绑定,从而就可以调用该版本的socket的各种函数了。 一句话解释:wsastartup()主要就是进行相应的socket库绑定

WSAStarup

  • W:windows
  • S:socket
  • A:Asynchronous异步
  • Starup:启动
int WSAStartup ( WORD wVersionRequested, LPWSADATA lpWSAData ); 

WSAStartup需要传入两个参数,wVersionRequested指明需要库的版本号,lpWSAData为指向WSAData数据结构的指针,用来接收Windows Sockets实现细节(存储初始化数据)

调用WSAStartup函数时,操作系统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。该函数执行成功后返回0。  否则返回对应错误的宏。

WORD是微软SDK中的类型,意为'字' ,是2byte无符号整数,表示范围0~65535相当于C语言中的2个char

MAKEWORD();可以创建WORD类型。其工作原理类似于bLow | bHigh<<8

WORD MAKEWORD(     //函数原型
BYTE bLow, //指定新变量的低字节序;
BYTE bHigh //指定新变量的高字节序;
);

在进行初始化结果判断时需判断两个:第一个是WSAStarup函数的返回值,判断返回结果是否为0;第二个是存储初始化数据WSAData的高位和地位是否和我们指定的库版本一致

//检查1
if (err != 0) {return err;
}
//检查2
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {//清理套接字库WSACleanup();return -1;
}
  • WSACleanup()函数 

int WSACleanup (void);

应用程序在完成对请求的Socket库的使用后,要调用WSACleanup函数来解除与Socket库的绑定并且释放Socket库所占用的系统资源。

创建套接字——socket

//创建套接字
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);

socket函数用于创建一个socket描述符,它唯一标识一个socket,包含了三个参数:

int socket(int domain, int type, int protocol);

  • domain:协议域/协议族

协议族决定了socket的地址类型,在通信中必须采用相应的地址.

  • type:socket通信类型 

函数socket()的参数type用于设置socket通信类型


并不是所有的协议族都实现了这些协议类型,例如,AF_INET协议族就没有实现SOCK_SEQPACKET协议类型。

类型为SOCK_STREAM的套接字表示一个双向的字节流,与管道类似。流式的套接字在进行数据收发之前必须已经连接,连接使用connect()函数进行。一旦连接,可以使用read()或者write()函数进行数据的传输。流式通信方式保证数据不会丢失或者重复接收,当数据在一段时间内任然没有接受完毕,可以将这个连接人为已经死掉。
SOCK_DGRAM和SOCK_RAW 这个两种套接字可以使用函数sendto()来发送数据,使用recvfrom()函数接受数据,recvfrom()接受来自制定IP地址的发送方的数据。
SOCK_PACKET是一种专用的数据包,它直接从设备驱动接受数据。

  • protocol:制定某个协议的特定类型

函数socket()的第3个参数protocol用于制定某个协议的特定类型,即type类型中的某个类型。通常某协议中只有一种特定类型,这样protocol参数仅能设置为0;但是有些协议有多种特定的类型,就需要设置这个参数来选择特定的类型。

绑定到本机 ——bind

1.首先准本绑定信息。

在准本绑定信息中既要指明绑定的IP地址,同时也要指明绑定的端口号

SOCKADDR_IN addrSrv;
//地址
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
//协议族,与上面保持一致
addrSrv.sin_family = AF_INET;
//端口;0~65535,其中1024以下的端口为系统保留的
addrSrv.sin_port = htons(6000);

sockaddr 和 sockaddr_in 这两个结构体用来处理网络通信的地址。

sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起了;sockaddr_in该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中

sockaddrsockaddr_in二者长度一样,都是16个字节,即占用的内存大小是一致的,因此可以互相转化。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。

sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息,是一种通用的套接字地址。 
    sockaddr_in 是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作,使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了。一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数:sockaddr_in用于socket定义和赋值;sockaddr用于函数参数。

htonl函数

主机的unsigned long值转换成网络字节顺序。主要针对32位的(long)

htons函数

htons()作用是将端口号由主机字节序转换为网络字节序的整数值。主要针对16位的(short)

htonl和htons函数

  • h:host主机
  • to:转换
  • n:network:网络
  • l:32位l的ong
  • s:16位的short

之所以要进行字节序的转换,是因为主机字节序和网络字节序的存储不同

主机字节序:
1)大端存储:低位字节放在内存的高地址端,高位字节放在内存的低地址端
2)小端存储:低位字节放在内存的低地址端,高位字节放在内存的高地址端

网络字节序:
UDP/TCP/IP协议规定:把接收到的第一个字节当作高位字节看待,这就要求发送端发送的第一个字节是高位字节;而在发送端发送数据时,发送的第一个字节是该数值在内存中的起始地址处对应的那个字节,也就是说,该数值在内存中的起始地址处对应的那个字节就是要发送的第一个高位字节(即:高位字节存放在低地址处);由此可见,多字节数值在发送之前,在内存中因该是以大端存储而一般x86计算机用的是小端存储~

在一些socket通信的服务器程序中我们会看到在服务器bind IP地址和端口号时,我们不是bind明确的IP地址(如222.20.79.150),而是使用INADDR_ANY。

INARRD_ANY是用于多网卡的机器上的,多网卡就会有多个IP地址。比如你的机器有3个IP:192.168.1.1、202.202.202.202和61.1.2.3。如果设置serv.sin_addr.s_addr=inet_addr("192.168.1.1");然后监听100端口,这时其他机器只有连接到192.168.1.1才能成功;连接202.202.202.202:100或61.1.2.3:100都会失败。如果设置serv.sin_addr.s_addr=htonl(INADDR_ANY);的话,无论连接哪个IP都可以连接上。

总的来说INADDR_ANY参数就表明可以连接到本机的所有ip都是可以的,极大的简化了需要创建socket的数量,因为我们就绑定一个INADDR_ANY和一个端口,然后客户端通信到这个机器的所有ip都用这个socket来处理。

2.绑定信息准备好后则进行绑定到本机

 //绑定
bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

bind()函数原型

int bind (int sockfd, const struct sockaddr * addr, socklen_t addrlen);

1.sockfd:即为socket描述字,他是通过socket()函数创建的,唯一标识一个socket。bind函数就是将这个描述子绑定一个名字

2.addr:一个sockaddr*指针,指向地址结构的指针,根据创建socket时的地址协议不同而不同。

3.addrlen:对应地址结构的长度。通常服务器在启动时会绑定一个众所周知的地址(ip地址+端口号)。而客户端不用指定系统自动分配,所以通常服务端在listen之前要调用bind(),而客户端不会调用,在connect()时由系统随机生成一个。

bind函数运行成功后会将端口和IP地址绑定到Socket描述符上,返回0时表示绑定成功。

开始监听 ——listen

//监听
listen(sockSrv, 10);

listen()函数原型

int listen(int sockfd, int backlog)

listen函数的第一个参数时即将要监听的socket描述字,第二个参数为相应的socket可以排队的最大连接数。socket()创建的socket默认是一个主动类型,listen则将socket变成被动类型,等待客户连接请求。

对于调用listen进行监听的套接字,操作系统会为其维护2个队列:未完成队列,已完成队列。

  • 当客户端发送TCP三次握手的第一次,服务器会在未完成队列创建一个对应项(之后就是等待TCP的第二次和第三次完成)
  • 当三次握手完成后,该连接就会放在完成队列中,接着就可以执行accept函数从队列中移出

listen的第二参数backlog则表示,这两个队列的和不能超过baklog。

这个过程就如下图所示:

连接客户端请求——accept

//接收请求前的准备工作
SOCKADDR addrCli;
int len = sizeof(SOCKADDR);while (true) {//接收链接请求,返回针客户端的套接字SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrCli, &len);//关闭连接closesocket(sockConn);
}

accept()函数

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • sockfd用来标识服务端套接字(也就是listen函数中设置为监听状态的套接字)
  • addr用来保存客户端套协议地址(包括客户端IP和端口信息等)
  • addrlen是客户端套接字的长度

返回客户端套接字的标识,一个客户端的socket

注意:如果没有客户端套接字去请求,它便会在那里一直等下去。如果是非阻塞式的socket, 那么accept函数会立即返回。

发送与接收数据 ——send/recv

char recvBuf[100];
char sendBuf[100];
while (true) {//接收连接请求,返回针对客户端的套接字SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrCli, &len);//准备发送的数据sprintf_s(sendBuf, 100, "hello world");//发送数据send(sockConn, sendBuf, sizeof(sendBuf) + 1, 0);//接收数据recv(sockConn, recvBuf, 100, 0);std::cout << recvBuf << std::endl;//关闭套接字closesocket(sockConn);
}
int send( SOCKET s, const char FAR *buf,  int len, int flags );
int recv( SOCKET s, char FAR *buf,  int len,  int flags );   
  • s指定客户端socket的描述符
  • buf为发送/接收数据的缓冲区
  • len为实际发送数据/接收数据的缓冲区大小
  • flags一般置位0

完整代码

#include <iostream>
#include<WinSock2.h>//第二版本的网络库
#pragma comment(lib,"ws2_32.lib")//一般模板
/*
*   0.初始化套接字库
*   1.创建socket
*   2.绑定到本机
*   3.开始监听
*   while(true){
*       4.接收客户端连接
*
*       5.关闭客户端socket
*   }
*
*   6.关闭服socket
*   7.清理套接字库
*/int main() {//初始化套接字库WORD wVersion;WSADATA wsaData;int err;wVersion = MAKEWORD(1, 1);  //MAKEWORD(a,b) b|a<<8err = WSAStartup(wVersion, &wsaData);//检查1if (err != 0) {return err;}//检查2if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {//清理套接字库WSACleanup();return -1;}//创建tcp套接字SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);//绑定到本机//绑定即要指明绑定的哪个IP地址,同时指明绑定的端口号//准备绑定信息SOCKADDR_IN addrSrv;addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//协议族,与上面保持一致addrSrv.sin_family = AF_INET;//端口;0~65535,其中1024以下的端口为系统保留的addrSrv.sin_port = htons(6000);//绑定bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));//监听listen(sockSrv, 10);std::cout << "Server start at 6000" << std::endl;//接收请求前的准备工作SOCKADDR addrCli;int len = sizeof(SOCKADDR);char recvBuf[100];char sendBuf[100];while (true) {//接收链接请求,返回针对客户端的套接字SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrCli, &len);//准备发送的数据sprintf_s(sendBuf, 100, "hello world");//发送数据send(sockConn, sendBuf, sizeof(sendBuf) + 1, 0);//接收数据recv(sockConn, recvBuf, 100, 0);std::cout << recvBuf << std::endl;//关闭套接字closesocket(sockConn);}//关闭套接字closesocket(sockSrv);//清理套接字库WSACleanup();system("pause");
}

TCP客户端

首先在同一解决方案下另建一个控制台应用,接着按照TCP客户端的实现流程编写代码

初始化套接字库——WSAStarup

客户端初始化套接字库的代码与服务端的基本上是一致的,要注意客户端和服务端的套接字库版本相一致。

//初始化套接字库
WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);  //MAKEWORD(a,b) b|a<<8
err = WSAStartup(wVersion, &wsaData);
//检查1
if (err != 0) {return err;
}
//检查2
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {//清理套接字库WSACleanup();return -1;
}

创建套接字

//创建tcp套接字,与服务器保持一致
SOCKET sockCli= socket(AF_INET, SOCK_STREAM, 0);
//绑定到本机
//绑定即要指明绑定的哪个IP地址,同时指明绑定的端口号
//准备绑定信息
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
//协议族,与上面保持一致
addrSrv.sin_family = AF_INET;
//端口;0~65535,其中1024以下的端口为系统保留的
addrSrv.sin_port = htons(6000);

客户端创建套接字要指明绑定的IP地址和端口。

iner_addr会检查传入的字符串是否是一个合法的IP地址(例如格式或者每个字段是否<255) ,如果非法则返回INADDR_NONE正确执行将返回一个无符号长整数型数

连接服务器——connect

connect(sockCli, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

connect()用于建立与指定socket的连接。

函数原型

int connect(SOCKET s, const struct sockaddr * name, int namelen);
  • s:标识一个未连接socket(客户端Socket
  • name:指向要连接套接字的sockaddr结构体的指针
  • namelen:sockaddr结构体的字节长度

connect操作之后代表对应的套接字已连接 ,发送与接收数据时就可以使用这个套接字。

发送与接收数据

//发送的数据
char sendBuf[] = "world";
//接收的数据
char recvBuf[100];
//发送数据到服务器
send(sockCli, sendBuf, strlen(sendBuf) + 1, 0);
//接收数据到服务器
recv(sockCli, recvBuf, sizeof(recvBuf), 0);

注意程序结束前要关闭套接字,清理套接字库。

//关闭套接字
closesocket(sockCli);
WSACleanup();

完整代码

#include <iostream>
#include<WinSock2.h>//第二版本的网络库
#pragma comment(lib,"ws2_32.lib")#include <iostream>int main() {//初始化套接字库WORD wVersion;WSADATA wsaData;int err;wVersion = MAKEWORD(1, 1);  //MAKEWORD(a,b) b|a<<8err = WSAStartup(wVersion, &wsaData);//检查1if (err != 0) {return err;}//检查2if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {//清理套接字库WSACleanup();return -1;}//创建tcp套接字,与服务器保持一致SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);//准备连接信息//指明要连接的IP地址和端口号SOCKADDR_IN addrSrv;addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//协议族,与上面保持一致addrSrv.sin_family = AF_INET;//端口;0~65535,其中1024以下的端口为系统保留的addrSrv.sin_port = htons(6000);//连接服务器connect(sockCli, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));//发送的数据char sendBuf[] = "world";//接收的数据char recvBuf[100];//发送数据到服务器send(sockCli, sendBuf, strlen(sendBuf) + 1, 0);//接收数据到服务器recv(sockCli, recvBuf, sizeof(recvBuf), 0);std::cout << recvBuf << std::endl;//关闭套接字closesocket(sockCli);WSACleanup();system("pause");return 0;
}

TCP套接字编程详解相关推荐

  1. 【网络】网络基础套接字编程详解

      目录 网络初识 1.网络协议初识 2.网络协议的分层: OSI分层模型--->网络理论模型: TCP/IOP五层模型--->工业中采用的网络模型 为什么要有网络分层? 3.网络数据的封 ...

  2. Socket 套接字原理详解

    Socket 套接字原理详解 socket 编程介绍 Socket编程封装了常见的TCP.UDP操作,可以实现非常方便的网络编程. socket() 函数介绍 # socket.socket(fami ...

  3. 套接字编程---2(TCP套接字编程的流程,TCP套接字编程中的接口函数,TCP套接字的实现,TCP套接字出现的问题,TCP套接字多进程版本,TCP套接字多线程版本)

    TCP模型创建流程图 TCP套接字编程中的接口 socket 函数 #include <sys/types.h> /* See NOTES */ #include <sys/sock ...

  4. tcp套接字编程模型

    1. tcp套接字编程 用下面的一张图可以清楚表示: 下面的python实现也很清晰: server: def tcplink(sock, addr):print 'Accept new connec ...

  5. 《计算机网络:自顶向下方法(原书第6版)》一2.7 TCP套接字编程

    本节书摘来华章计算机<计算机网络:自顶向下方法(原书第6版)>一书中的第2章 ,第2.7节,(美)James F.Kurose Keith W.Ross 著 陈 鸣 译 更多章节内容可以访 ...

  6. 《网络编程》基本 TCP 套接字编程

    在进行套接字编程之前必须熟悉其地址结构,有关套接字的地址结构可参考文章<套接字编程简介>.基于 TCP 的套接字编程的所有客户端和服务器端都是从调用socket 开始,它返回一个套接字描述 ...

  7. 计算机网络实验4 - TCP套接字编程 - 点对点聊天 - 代码实现

    客户端 package chat;import java.io.IOException; import java.io.PrintStream; import java.net.Socket; imp ...

  8. 《UNIX网络编程 卷1:套接字联网API》学习笔记——基本TCP套接字编程

    UNIX网络编程--基本TCP套接字编程 socket 函数 connect 函数 bind 函数 listen 函数 accept 函数 fork 和 exec 函数 并发服务器 close 函数 ...

  9. UNIX环境编程(c语言)--套接字--基本TCP套接字编程

    目录 准备知识 字节序 字节序转换函数 字节操纵函数 地址转换函数 地址结构 基本TCP套接字编程 概要 socket函数 bind函数 listen函数 accept函数 connect函数 通信函 ...

最新文章

  1. 一页纸项目管理方法(OOPM)背后的思想
  2. GODOT 3.0 开发进度汇报 #6
  3. wsl设置c盘自动挂载到wsl中的/c/目录下
  4. android nfc peer to peer 实例,NFC Peer2Peer Mode - Android Beam - ISO 18092
  5. leetcode 872. 叶子相似的树(dfs)
  6. Print() 语句以及数字赋值语句 中if-else的使用
  7. Loading 遮蔽层 简单实现。
  8. php源码自动识别文本中的链接,自动加载识别文件Auto.php
  9. linux终端怎样ise,Linux下ISE开发环境的安装zz
  10. 解决由于sz rz导致抓包时文件容量增加
  11. reactive streams的Mono及Flux
  12. asp ado 连接测试_ASP.NET Core 简介
  13. 日志易数据接入之 Syslog 日志上传
  14. 基于法律裁判文书的法律判决大数据预测
  15. linux的ffmpeg转换视频为mp4,使用开源软件FFmpeg将各种格式视频转换成MP4视频格式(最简单方法)...
  16. PVE下的黑群晖的其他后续设置
  17. 韩国创业公司Hdac试图通过区块链保障物联网未来
  18. 银行软件测试工作总结
  19. pandas 之 to_csv 保存数据出现中文乱码问题及解决办法
  20. vba的change事件

热门文章

  1. fedora24安装xmind7
  2. Jmeter接口测试——配置全局token
  3. Windows下PHP版本切换
  4. Linux自学指南-从应用到内核
  5. STemwin小工具应用
  6. vue-cli3.x项目使用tinymce富文本编辑器
  7. 关于机器人状态估计/VIO/VSLAM中能观性/可观性/FEJ的一些直接解释
  8. 横店影视城:山间腹地崛起文化产业 小镇织就光影梦
  9. 【系统集成项目管理刷题专题】第12章—项目沟通管理和干系人管理
  10. php 判断数组值为空,PHP判断数组是否为空的五种常用方法