西安网络整合营销,优化推广网站,高明搜索seo,品质好价格低的广告词最基本的 Socket 模型
参考这次答应我#xff0c;一举拿下 I/O 多路复用#xff01; (qq.com)
Socket编程详解-CSDN博客
Socket是一种通信机制#xff0c;通过它可以在不同主机之间进行数据交换。在Socket编程中#xff0c;有两种常见的通信模式#xff1a;客户端-服务…最基本的 Socket 模型
参考这次答应我一举拿下 I/O 多路复用 (qq.com)
Socket编程详解-CSDN博客
Socket是一种通信机制通过它可以在不同主机之间进行数据交换。在Socket编程中有两种常见的通信模式客户端-服务器模式和点对点模式。它基于TCP/IP协议栈并使用IP地址和端口号来标识通信的目标。
Socket 的中文名叫作套接字欲建立连接的两个进程各自有一个socket来标识那么这两个socket组成的socket pair就唯一标识一个连接。因此可以用Socket来描述网络连接的一对一关系。很像弄了一根网线一头插在客户端一头插在服务端然后进行通信。“IP地址端口号”就对应一个socket。 在网络通信中套接字一定是成对出现的。一端的发送缓冲区对应对端的接收缓冲区。我们使用同一个文件描述符索发送缓冲区和接收缓冲区。 服务端首先调用socket()函数创建网络协议为 IPv4以及传输协议为 TCP 的 Socket 接着调用bind()函数给这个 Socket 绑定一个 IP 地址和端口绑定这两个的目的是什么 绑定端口的目的当内核收到 TCP 报文通过 TCP 头里面的端口号来找到我们的应用程序然后把数据传递给我们。 绑定 IP 地址的目的一台机器是可以有多个网卡的每个网卡都有对应的 IP 地址当绑定一个网卡时内核在收到该网卡上的包才会发给我们
绑定完 IP 地址和端口后就可以调用listen()函数进行监听此时对应 TCP 状态图中的 listen如果我们要判定服务器中一个网络程序有没有启动可以通过 netstate 命令查看对应的端口号是否有被监听。
服务端进入了监听状态后通过调用accept()函数来从内核获取客户端的连接如果没有客户端连接则会阻塞等待客户端连接的到来。
那客户端是怎么发起连接的呢客户端在创建好 Socket 后调用connect()函数发起连接该函数的参数要指明服务端的 IP 地址和端口号然后万众期待的 TCP 三次握手就开始了。
在 TCP 连接的过程中服务器的内核实际上为每个 Socket 维护了两个队列 一个是还没完全建立连接的队列称为 TCP 半连接队列这个队列都是没有完成三次握手的连接此时服务端处于syn_rcvd的状态 一个是一件建立连接的队列称为 TCP 全连接队列这个队列都是完成了三次握手的连接此时服务端处于established状态
当 TCP 全连接队列不为空后服务端的accept()函数就会从内核中的 TCP 全连接队列里拿出一个已经完成连接的 Socket 返回应用程序后续数据传输都用这个 Socket。
注意监听的 Socket 和真正用来传数据的 Socket 是两个 一个叫作监听 Socket 一个叫作已连接 Socket
连接建立后客户端和服务端就开始相互传输数据了双方都可以通过read()和write()函数来读写数据。
至此 TCP 协议的 Socket 程序的调用过程就结束了
基于 Linux 一切皆文件的理念在内核中 Socket 也是以「文件」的形式存在的也是有对应的文件描述符(内核可以通过文件描述符找到对应打开的文件)
相关知识
参考CC socket网络编程扫盲篇_c端口号的数据类型-CSDN博客
大小端存储
大小端是针对非单字节数据的存取比如short型int型等。大端存储是将数据的高位存储在内存的低地址处小端存储是将数据的低位存储在内存的低地址处。 为什么会有大小端 简单点说就是硬件厂商各有所好并没有统一的约定制作制作哪一个大端的优势在于第一个字节就是高位很容易判断正负性。小端的优势在于第一个字节是低位最后一个字节是高位可以依次取出相应的字节进行运算并且最终会把符号位刷新这样运算起来更高效。 内存中的多字节数据相对于内存地址有大端和小端之分磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分。网络数据流同样有大端小端之分那么如何定义网络数据流的地址呢发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出接收主机把从网络上接到的字节依次保存在接收缓冲区中也是按内存地址从低到高的顺序保存因此网络数据流的地址应这样规定先发出的数据是低地址后发出的数据是高地址。
TCP/IP协议规定网络数据流应采用大端字节序即低地址高字节。
IP地址IP Address
计算机分布在世界各地要想和它们通信必须要知道确切的位置。确定计算机位置的方式有多种IP 地址是最常用的例如114.114.114.114 是国内第一个、全球第三个开放的 DNS 服务地址127.0.0.1 是本机地址。
其实我们的计算机并不知道 IP 地址对应的地理位置当要通信时只是将 IP 地址封装到要发送的数据包中交给路由器去处理。路由器有非常智能和高效的算法很快就会找到目标计算机并将数据包传递给它完成一次单向通信。
目前大部分软件使用 IPv4 地址但 IPv6 也正在被人们接受尤其是在教育网中已经大量使用。
端口Port
有了 IP 地址虽然可以找到目标计算机但仍然不能进行通信。一台计算机可以同时提供多种网络服务例如Web服务、FTP服务文件传输服务、SMTP服务邮箱服务等仅有 IP 地址计算机虽然可以正确接收到数据包但是却不知道要将数据包交给哪个网络程序来处理所以通信失败。
为了区分不同的网络程序计算机会为每个网络程序分配一个独一无二的端口号Port Number例如Web服务的端口号是 80FTP 服务的端口号是 21SMTP 服务的端口号是 25。
端口Port是一个虚拟的、逻辑上的概念。可以将端口理解为一道门数据通过这道门流入流出每道门有不同的编号就是端口号。如下图所示 协议Protocol
协议Protocol就是网络通信的约定通信的双方必须都遵守才能正常收发数据。协议有很多种例如 TCP、UDP、IP 等通信的双方必须使用同一协议才能通信。协议是一种规范由计算机组织制定规定了很多细节例如如何建立连接如何相互识别等。
协议仅仅是一种规范必须由计算机软件来实现。例如 IP 协议规定了如何找到目标计算机那么各个开发商在开发自己的软件时就必须遵守该协议不能另起炉灶。
所谓协议族Protocol Family就是一组协议多个协议的统称。最常用的是 TCP/IP 协议族它包含了 TCP、IP、UDP、Telnet、FTP、SMTP 等上百个互为关联的协议由于 TCP、IP 是两种常用的底层协议所以把它们统称为 TCP/IP 协议族。
数据传输方式
计算机之间有很多数据传输方式各有优缺点常用的有两种SOCK_STREAM(使用TCP协议) 和 SOCK_DGRAM(使用UDP协议)。
SOCK_STREAM 表示面向连接的数据传输方式。数据可以准确无误地到达另一台计算机如果损坏或丢失可以重新发送但效率相对较慢。常见的 http 协议就使用 SOCK_STREAM 传输数据因为要确保数据的正确性否则网页不能正常解析。 SOCK_DGRAM 表示无连接的数据传输方式。计算机只管传输数据不作数据校验如果数据在传输中损坏或者没有到达另一台计算机是没有办法补救的。也就是说数据错了就错了无法重传。因为 SOCK_DGRAM 所做的校验工作少所以效率比 SOCK_STREAM 高。
QQ 视频聊天和语音聊天就使用 SOCK_DGRAM 传输数据因为首先要保证通信的效率尽量减小延迟而数据的正确性是次要的即使丢失很小的一部分数据视频和音频也可以正常解析最多出现噪点或杂音不会对通信质量有实质的影响。
注意SOCK_DGRAM 没有想象中的糟糕不会频繁的丢失数据数据错误只是小概率事件。
有可能多种协议使用同一种数据传输方式所以在 socket 编程中需要同时指明数据传输方式和协议。
综上所述IP地址和端口能够在广袤的互联网中定位到要通信的程序协议和数据传输方式规定了如何传输数据有了这些两台计算机就可以通信了。
Socket编程基本步骤
参考Socket编程详解从基本概念到实例应用(TCP|UDP C语言实例详解)-CSDN博客
常用函数的原型和参数用法详解
要使用Socket函数之前要先包含两个头文件
#include sys/types.h
#include socket.h
1.socket()函数
int socket(int domain, int type, int protocol); domain地址族通常为AF_INETIPv4或AF_INET6IPv6。 type套接字类型可以是SOCK_STREAMTCP或SOCK_DGRAMUDP。 protocol协议通常为0表示自动选择合适的协议。
2.bind()函数
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); sockfd套接字描述符。 addr指向本地地址结构体的指针。 addrlen地址结构体的长度。
3.listen()函数
int listen(int sockfd, int backlog); sockfd套接字描述符。 backlog待连接队列的最大长度。
4.accept()函数
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); sockfd套接字描述符。 addr指向客户端地址结构体的指针用于存储客户端的地址信息。 addrlen指向地址结构体长度的指针。
5.connect()函数
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); sockfd套接字描述符。 addr指向目标地址结构体的指针。 addrlen地址结构体的长度。
6.send()函数
ssize_t send(int sockfd, const void *buf, size_t len, int flags); sockfd套接字描述符。 buf指向要发送数据的缓冲区的指针。 len要发送的数据的长度。 flags发送标志通常为0。
7.recv()函数
ssize_t recv(int sockfd, void *buf, size_t len, int flags); sockfd套接字描述符。 buf指向接收数据的缓冲区的指针。 len要接收的数据的最大长度。 flags接收标志通常为0。
8.sendto()函数
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); sockfd套接字描述符。 buf指向要发送数据的缓冲区的指针。 len要发送的数据的长度。 flags发送标志通常为0。 dest_addr指向目标地址结构体的指针。 addrlen目标地址结构体的长度。
9.recvfrom()函数
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); sockfd套接字描述符。 buf指向接收数据的缓冲区的指针。 len要接收的数据的最大长度。 flags接收标志通常为0。 src_addr指向源地址结构体的指针用于存储源地址信息。 addrlen指向地址结构体长度的指针。
10.close()函数
int close(int sockfd); sockfd套接字描述符。
通过使用这些函数可以实现TCP和UDP通信的服务器端和客户端程序。TCP提供可靠的、面向连接的通信适用于需要确保数据可靠性和顺序性的场景而UDP提供无连接的通信适用于实时性要求较高但可靠性要求较低的场景。
TCP开发流程
1.服务器端
创建套接字int serverSocket socket(AF_INET, SOCK_STREAM, 0);绑定地址struct sockaddr_in serverAddress; serverAddress.sin_family AF_INET; serverAddress.sin_port htons(port); serverAddress.sin_addr.s_addr INADDR_ANY; bind(serverSocket, (struct sockaddr*)serverAddress, sizeof(serverAddress));监听连接listen(serverSocket, backlog);接受连接struct sockaddr_in clientAddress; int clientSocket accept(serverSocket, (struct sockaddr*)clientAddress, sizeof(clientAddress));发送和接收数据send(clientSocket, buffer, size, 0); recv(clientSocket, buffer, size, 0);关闭连接close(clientSocket); close(serverSocket);
2.客户端
创建套接字int clientSocket socket(AF_INET, SOCK_STREAM, 0);连接服务器struct sockaddr_in serverAddress; serverAddress.sin_family AF_INET; serverAddress.sin_port htons(port); serverAddress.sin_addr.s_addr inet_addr(serverIP); connect(clientSocket, (struct sockaddr*)serverAddress, sizeof(serverAddress));发送和接收数据send(clientSocket, buffer, size, 0); recv(clientSocket, buffer, size, 0);关闭连接close(clientSocket);
UDP开发流程
1.服务器端
创建套接字int serverSocket socket(AF_INET, SOCK_DGRAM, 0);绑定地址struct sockaddr_in serverAddress; serverAddress.sin_family AF_INET; serverAddress.sin_port htons(port); serverAddress.sin_addr.s_addr INADDR_ANY; bind(serverSocket, (struct sockaddr*)serverAddress, sizeof(serverAddress));接收和发送数据recvfrom(serverSocket, buffer, size, 0, (struct sockaddr*)clientAddress, sizeof(clientAddress)); sendto(serverSocket, buffer, size, 0, (struct sockaddr*)clientAddress, sizeof(clientAddress));关闭套接字close(serverSocket);
2.客户端
创建套接字int clientSocket socket(AF_INET, SOCK_DGRAM, 0);发送和接收数据sendto(clientSocket, buffer, size, 0, (struct sockaddr*)serverAddress, sizeof(serverAddress)); recvfrom(clientSocket, buffer, size, 0, (struct sockaddr*)serverAddress, sizeof(serverAddress));关闭套接字close(clientSocket);
Socket编程实例
以下是使用C语言通过Socket实现TCP和UDP通信的服务端和客户端通信程序的示例
TCP通信示例
服务端server.c
#include stdio.h
#include stdlib.h
#include string.h
#include sys/socket.h
#include arpa/inet.h#define SERVER_IP 127.0.0.1
#define SERVER_PORT 8080
#define BUFFER_SIZE 1024int main() {int serverSocket, clientSocket;struct sockaddr_in serverAddr, clientAddr;socklen_t clientAddrLen;char buffer[BUFFER_SIZE];// 创建服务端套接字serverSocket socket(AF_INET, SOCK_STREAM, 0);if (serverSocket -1) {perror(Failed to create socket);exit(EXIT_FAILURE);}// 设置服务器地址信息memset(serverAddr, 0, sizeof(serverAddr));serverAddr.sin_family AF_INET;serverAddr.sin_port htons(SERVER_PORT);serverAddr.sin_addr.s_addr htonl(INADDR_ANY);// 绑定套接字到指定地址和端口if (bind(serverSocket, (struct sockaddr *)serverAddr, sizeof(serverAddr)) -1) {perror(Failed to bind socket);exit(EXIT_FAILURE);}// 监听连接请求if (listen(serverSocket, 5) -1) {perror(Failed to listen);exit(EXIT_FAILURE);}printf(Server listening on port %d...\n, SERVER_PORT);// 接受客户端连接clientAddrLen sizeof(clientAddr);clientSocket accept(serverSocket, (struct sockaddr *)clientAddr, clientAddrLen);if (clientSocket -1) {perror(Failed to accept client connection);exit(EXIT_FAILURE);}printf(Client connected: %s\n, inet_ntoa(clientAddr.sin_addr));// 接收数据memset(buffer, 0, sizeof(buffer));if (recv(clientSocket, buffer, BUFFER_SIZE, 0) -1) {perror(Failed to receive data);exit(EXIT_FAILURE);}printf(Client message: %s\n, buffer);// 发送响应strcpy(buffer, Hello, Client!);if (send(clientSocket, buffer, strlen(buffer), 0) -1) {perror(Failed to send data);exit(EXIT_FAILURE);}// 关闭套接字close(clientSocket);close(serverSocket);return 0;
}
客户端client.c
#include stdio.h
#include stdlib.h
#include string.h
#include sys/socket.h
#include arpa/inet.h#define SERVER_IP 127.0.0.1
#define SERVER_PORT 8080
#define BUFFER_SIZE 1024int main() {int clientSocket;struct sockaddr_in serverAddr;char buffer[BUFFER_SIZE];// 创建客户端套接字clientSocket socket(AF_INET, SOCK_STREAM, 0);if (clientSocket -1) {perror(Failed to create socket);exit(EXIT_FAILURE);}// 设置服务器地址信息memset(serverAddr, 0, sizeof(serverAddr));serverAddr.sin_family AF_INET;serverAddr.sin_port htons(SERVER_PORT);if (inet_pton(AF_INET, SERVER_IP, (serverAddr.sin_addr)) 0) {perror(Failed to set server IP);exit(EXIT_FAILURE);}// 连接到服务器if (connect(clientSocket, (struct sockaddr *)serverAddr, sizeof(serverAddr)) -1) {perror(Failed to connect to server);exit(EXIT_FAILURE);}printf(Connected to server %s:%d\n, SERVER_IP, SERVER_PORT);// 发送数据strcpy(buffer, Hello, Server!);if (send(clientSocket, buffer, strlen(buffer), 0) -1) {perror(Failed to send data);exit(EXIT_FAILURE);}// 接收响应memset(buffer, 0, sizeof(buffer));if (recv(clientSocket, buffer, BUFFER_SIZE, 0) -1) {perror(Failed to receive data);exit(EXIT_FAILURE);}printf(Server response: %s\n, buffer);// 关闭套接字close(clientSocket);return 0;
}
UDP通信示例
服务端server.c
#include stdio.h
#include stdlib.h
#include string.h
#include sys/socket.h
#include arpa/inet.h#define SERVER_IP 127.0.0.1
#define SERVER_PORT 8080
#define BUFFER_SIZE 1024int main() {int serverSocket;struct sockaddr_in serverAddr, clientAddr;socklen_t clientAddrLen;char buffer[BUFFER_SIZE];// 创建服务端套接字serverSocket socket(AF_INET, SOCK_DGRAM, 0);if (serverSocket -1) {perror(Failed to create socket);exit(EXIT_FAILURE);}// 设置服务器地址信息memset(serverAddr, 0, sizeof(serverAddr));serverAddr.sin_family AF_INET;serverAddr.sin_port htons(SERVER_PORT);serverAddr.sin_addr.s_addr htonl(INADDR_ANY);// 绑定套接字到指定地址和端口if (bind(serverSocket, (struct sockaddr *)serverAddr, sizeof(serverAddr)) -1) {perror(Failed to bind socket);exit(EXIT_FAILURE);}printf(Server listening on port %d...\n, SERVER_PORT);// 接收数据clientAddrLen sizeof(clientAddr);memset(buffer, 0, sizeof(buffer));if (recvfrom(serverSocket, buffer, BUFFER_SIZE, 0, (struct sockaddr *)clientAddr, clientAddrLen) -1) {perror(Failed to receive data);exit(EXIT_FAILURE);}printf(Client message: %s\n, buffer);// 发送响应strcpy(buffer, Hello, Client!);if (sendto(serverSocket, buffer, strlen(buffer), 0, (struct sockaddr *)clientAddr, clientAddrLen) -1) {perror(Failed to send data);exit(EXIT_FAILURE);}// 关闭套接字close(serverSocket);return 0;
}
客户端client.c
#include stdio.h
#include stdlib.h
#include string.h
#include sys/socket.h
#include arpa/inet.h#define SERVER_IP 127.0.0.1
#define SERVER_PORT 8080
#define BUFFER_SIZE 1024int main() {int clientSocket;struct sockaddr_in serverAddr;char buffer[BUFFER_SIZE];// 创建客户端套接字clientSocket socket(AF_INET, SOCK_DGRAM, 0);if (clientSocket -1) {perror(Failed to create socket);exit(EXIT_FAILURE);}// 设置服务器地址信息memset(serverAddr, 0, sizeof(serverAddr));serverAddr.sin_family AF_INET;serverAddr.sin_port htons(SERVER_PORT);if (inet_pton(AF_INET, SERVER_IP, (serverAddr.sin_addr)) 0) {perror(Failed to set server IP);exit(EXIT_FAILURE);}printf(Connected to server %s:%d\n, SERVER_IP, SERVER_PORT);// 发送数据strcpy(buffer, Hello, Server!);if (sendto(clientSocket, buffer, strlen(buffer), 0, (struct sockaddr *)serverAddr, sizeof(serverAddr)) -1) {perror(Failed to send data);exit(EXIT_FAILURE);}// 接收响应memset(buffer, 0, sizeof(buffer));if (recvfrom(clientSocket, buffer, BUFFER_SIZE, 0, NULL, NULL) -1) {perror(Failed to receive data);exit(EXIT_FAILURE);}printf(Server response: %s\n, buffer);// 关闭套接字close(clientSocket);return 0;
}