2015 字
5 分钟
TCP&IP网络编程学习札记_1
2026-06-23
浏览量 166 · 访客 12

打算专门开一期学习札记系列,主要是重新提炼在学习中遇到的一些重点来反复理解。

第一章:理解网络编程和套接字#

1.1 网络编程和套接字#

网络编程就是编写程序让两台连网的计算机相互交换数据,和我们平时理解的编程差不多,只不过网络编程目的主要是进行网络数据传输。
套接字"socket""socket"),socketsocket就是插座的意思。顾名思义,套接字的作用也类似于我们充电时的插座(连接电网来供电),它是用于网络数据传输的一个软件设备。
我们在用CC语言进行网络编程时会用socketsocket函数创建一个套接字。大体如下。

#include <sys/socket.h>
int socket(int domain, int type, int protocol);
//这个函数成功时会返回文件描述符,失败时返回-1。

然后我们需要绑定地址信息(IPIP地址和端口号)。

#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
//成功时返回0,失败时返回-1。

接着我们需要一个接听的函数,将套接字转化为可接听(连接)状态。

#include <sys/socket.h>
int listen(int sockfd, int backlog);
//成功时返回0, 失败时返回-1。

最后我们需要能够接受连接请求。

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//成功时返回文件描述符,失败时返回-1。

上述主要是针对服务器端的一些操作,针对客户端,我们只需要一个套接字和连接connectconnect的函数。

#include <sys/socket.h>
int connect(int sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
//成功时返回文件描述符,失败时返回-1。

原书中的这里有一个编写"HelloWorld!""Hello World!"的服务器端和客户端源码,可以在本地进行相互的传输数据,我这里就不具体演示了,感兴趣可以去看原书自己动手试一下。我这里主要记述一下一些重要的知识点。

1.2 基于Linux的文件操作#

这部分主要是有关于LinuxLinux的基本知识,由于对我来说不算新的知识点,所以我会相对简单地说明。
LinuxLinux中,socketsocket也被认为是文件的一种,所以也可以使用文件操作符来进行操作。系统分配标准输入,输出,错误的文件描述符分别为0,1,20, 1, 2。创建新文件(或套接字)就会分配新的文件描述符。
文件描述符简单来说就是给要操作的文件标个序号,然后操作时指定这个序号就是对其进行操作了。关于文件操作主要和下面几个函数有关:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
//path:文件名的字符串地址,flag文件打开模式信息
int open(const char *path, int flag);
//成功时返回文件描述符,失败时返回-1。
//关闭对应的套接字或文件的文件描述符
int close(int fd);
//成功时返回0,失败时返回-1。
//fd:对应文件的文件描述符,buf:保存传输数据的缓冲地址值,nbytes:传输的字节数
ssize_t write(int fd, const void * buf, size_t nbytes);
//成功时返回写入的字节数,失败时返回-1。
//fd和buf同上,nbytes:能接收数据的最大字节数
ssize_t read(int fd, void * buf, size_t nbytes);
//成功时返回接收的字节数(遇到文件结尾就返回0),失败时返回-1。

此外文件描述符是从小到大的顺序编号,一般从33开始,因为0,1,20, 1, 2是分配个标准I/OI/O的描述符。

1.3 基于Windows平台的实现#

这里关于WindowsWindows的实现也有编写"HelloWorld!""Hello World!"的服务器端和客户端源码,这里同样不再展示。主要讲讲WindowsWindowslinuxlinux使用套接字的不同之处。
WinsockWinsock进行套接字编程时我们需要使用WSAStartupWSAStartup函数,设置程序用到的WinsockWinsock版本,并初始化相应版本的库。

#include <winsock2.h>
//wVersionRequested:程序员要用的Winsock版本信息,lpWSAData:WSADATA结构体变量的地址
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
//成功时返回0,失败时返回非零的错误代码。

这里的版本信息我们使用MAKEWORDMAKEWORD宏函数构建WORDWORD型版本信息:

MAKEWORD(1, 2); //主版本为1, 副版本为2,返回0x0201。
MAKEWORD(2, 2); //主版本为2, 副版本为2,返回0x0202。

第二个参数我们就要传入WSADATAWSADATA类型的结构体变量的地址。(我们需要先初始化一个WSADATAWSADATA类型的变量),参考下面的演示:

int main(int argc, char* argv[]){
WSADATA wsaData;
....
if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error!");
....
return 0;
}

创建完之后我们就可以通过套接字传输数据了,而在套接字传输完之后我们还需要注销该库。

#include <winsock2.h>
int WSACleanup(void);
//成功时返回0,失败时返回SOCKET_ERROR。

调用完该函数之后,WinsockWinsock库会返还给操作系统,无法再调用WinsockWinsock相关函数。

1.4 基于Windows 的套接字相关函数及示例#

基于WindowsWindows的套接字相关函数如下:

#include <winsock2.h>
SOCKET socket(int af, int type, int protocol);
//成功时返回套接字句柄,失败时返回INVALID_SOCKET。
int bind(SOCKET s, const struct sockaddr * name, int namelen);
//成功时返回0,失败时返回SOCKET_ERROR。
int listen(SOCKET s, int backlog);
//成功时返回0,失败时返回SOCKET_ERROR。
SOCKET accpet(SOCKET s, struct sockaddr * addr, int * addrlen);
//成功时返回套接字句柄,失败时返回INVALID_SOCKET。
int connect(SOCKET s, const struct sockaddr * name, int namelen);
//成功时返回0,失败时返回SOCKET_ERROR。
int closesocket(SOCKET s);
//成功时返回0,失败时返回SOCKET_ERROR。

WindowsWindows中会区分文件句柄和套接字句柄,这一点不同于LinuxLinux文件描述符,所以我们在WindowsWindows创建socketsocket函数需要用SOCKETSOCKET来存储套接字句柄。
LinuxLinux中由于套接字也算是文件,所以可以使用文件I/OI/O函数的readreadwritewrite来进行数据传输。但WindowsWindows严格区分I/OI/O函数和套接字I/OI/O函数,下面介绍WinsockWinsock数据传输函数:

#include <winsock2.h>
//s:套接字句柄,buf和之前一样,len:要传输的字节数,flags;传输数据时用到的多种选项信息
int send(SOCKET s, const char * buf, int len, int flags);
//成功时返回传输字节数,失败时返回SOCKET_ERROR。
//len:能接收的最大字节数,其他的变量同上。
int recv(SOCKET s, const char * buf, int len, int flags);
//成功时返回接收的字节数(收到EOF时为0),失败时返回SOCKET_ERROR。

这里还要注意LinuxLinux中也是有sendsendrecvrecv函数的,WindowsWindowssendsendrecvrecv函数并不等价于LinuxLinuxread,writeread, write函数。

第二章:套接字类型与协议设置#

2.1 套接字协议及其数据传输特性#

协议简单来说就是为了完成数据交换而定好的约定(通信规则)。
这里需要深入理解一下第一章提到的scoketscoket函数。

#include <sys/socket.h>
int socket(int domain, int type, int protocol);
//domain: 套接字使用的协议族(Protocol Family)信息。
//type: 套接字数据传输类型信息。
//protocol: 计算机通信中使用的协议信息。

协议族ProtocolFamilyProtocol Family),也就是套接字通信中使用的一个协议分类。比较常用的是IPv4IPv4协议族。

名 称协 议 族
PF_INETIPv4互联网协议族
PF_INET6IPv6互联网协议族
PF_LOCAL本地通信的UNIX协议族
PF_PACKET底层套接字的协议族
PF_IPXIPX Novell协议族

一个协议族会有多种数据传输方式,而套接字类型TypeType),会决定套接字的数据传输方式。
这里有两种代表性的数据传输方式:

套接字类型1:面向连接的套接字(SOCK_STREAM)#

主要有三个特征:
1.传输过程中数据不会消失。
2.按序传输数据。
3.传输的数据不存在数据边界(BoundaryBoundary)。
总结成一句话就是可靠的,按序传递的,基于字节的面向连接的数据传输方式的套接字
在套接字内部有缓冲(BufferBuffer)也就是字节数组。通过套接字传输的数据将保存在该数组。所以我们在接收到数据后不一定要立马readread读取,可以在缓冲区填满了之后再读取。因此在面向连接的套接字中readreadwritewrite函数的调用次数并不重要,这也说明了这种套接字不存在数据边界。

套接字类型2:面向消息的套接字(SOCK_DGRAM)#

有四个主要特征:
1.强调快速传输而非传输顺序。
2.传输的数据可能丢失也可能损毁。
3.传输的数据有数据边界。
4.限制每次传输数据的大小。
同样总结成一句话:不可靠的,不按序传递的,以数据高速运输为目的的套接字
数据边界:每次传输次数和接收次数需要相同,这种特性就是“传输的数据具有边界”。
面向消息的套接字比面向连接的套接字具有更快的传输速度,但是无法保证数据损毁或丢失,且不存在连接这个概念。

协议的最终选择#

第三个参数一般情况下无需考虑直接填00即可。除非存在“同一个协议族中存在多个数据传输方式相同的协议”,数据传输方式相同,但是协议不同。此时就算需要通过第三个参数具体指定协议信息。
IPv4协议族中面向连接的套接字:

int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

满足前两个参数的指定的协议只有IPPROTO_TCPIPPROTO\_TCP协议,所以这种套接字也叫做TCP套接字
IPv4协议族中面向消息的套接字:

int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);

同样满足前两参数的指定的协议只有IPPROTO_UDPIPPROTO\_UDP协议,所以这种套接字也叫UDP套接字
这里原书中同样给出了TCPTCP套接字使用的示例,跟第一章的代码差不多,只不过多演示了一下writewritereadread调用次数不同仍然能完整传递数据的特性。

2.2 Windows平台下的实现及验证#

Windows操作系统下的socketsocket函数:

#include <winsock2.h>
SOCKET socket(int af, int type, int protocol);
//成功时返回socket句柄,失败时返回INVALID_SOCKET。

这里原作者提到了一些扩展性问题,尽管WindowsWindows操作系统下SOCKETSOCKET数据类型返回的也是intint整型,我们还是尽量使用SOCKETSOCKET而非intint接收返回值。同理于INVALID_SOCKETINVALID\_SOCKET
WindowsWindows下的TCPTCP套接字使用示例这里同样也不再赘述。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

TCP&IP网络编程学习札记_1
https://mkrari.cn/posts/tcp_ip_learn_1/
作者
Mkrari
发布于
2026-06-23
许可协议
CC BY-NC-SA 4.0