当前位置: 首页 > news >正文

windows与linux环境下网络编程

windows与linux环境进行网络编程,使用的库是不一样的,

下面主要说一下windows环境下的网络编程;

网络编程从大的方面说就是对信息的发送到接收,中间传输为物理线路的作用。网络编程最主要的工作就是在发送端把信息通过规定好的协议进行组装包,在接收端按照规定好的协议把包进行解析,从而提取出对应的信息,达到通信的目的。中间最主要的就是数据包的组装,数据包的过滤,数据包的捕获,数据包的分析。

网络编程的本质是让不同计算机上的进程通过网络交换数据。其核心依赖两个关键体系:

1. 协议栈:TCP/IP模型

计算机网络通过分层协议实现通信,实际应用中以TCP/IP四层模型为核心:

  • 链路层:处理硬件设备(如网卡)的物理数据传输(如以太网帧)。
  • 网络层:负责跨网络的数据包路由(核心协议:IP协议,定义数据包格式和地址)。
  • 传输层:提供端到端的通信服务(核心协议:TCP、UDP)。
  • 应用层:定义具体业务规则(如HTTP、FTP,由开发者实现)。

Socket接口主要工作在传输层和网络层,屏蔽了底层硬件和路由细节,让开发者可直接通过“端口+IP”定位目标进程。

2. 核心协议:TCP与UDP

传输层的两个核心协议决定了通信方式的差异:

  • TCP(传输控制协议)
    • 面向连接:通信前需通过“三次握手”建立连接,结束后“四次挥手”释放连接。
    • 可靠传输:通过确认机制、重传机制、流量控制保证数据不丢失、不重复、按序到达。
    • 字节流:数据以连续字节流形式传输(无边界)。
    • 适用场景:文件传输、网页访问等需可靠数据的场景。
  • UDP(用户数据报协议)
    • 无连接:直接发送数据,无需建立连接。
    • 不可靠传输:不保证数据到达,可能丢失、乱序。
    • 数据报:数据以独立“数据包”形式传输(有边界)。
    • 适用场景:视频通话、实时游戏等对延迟敏感的场景。
3. Socket(套接字)

Socket是操作系统提供的网络通信抽象,本质是一个“文件描述符”(类似文件句柄),通过它可读写网络数据。

  • 每个Socket绑定一个IP地址+端口号,唯一标识网络中的一个进程(IP定位计算机,端口定位进程)。
  • 分类:根据传输层协议,分为流式套接字(SOCK_STREAM,基于TCP)数据报套接字(SOCK_DGRAM,基于UDP)

二、核心预备知识

在编写代码前,需掌握两个关键技术点:地址结构字节序转换

1. 网络地址结构

Socket通过结构体描述网络地址,最常用的是IPv4地址结构sockaddr_in(定义在<netinet/in.h>):

struct sockaddr_in {sa_family_t     sin_family;   // 地址族:必须为AF_INET(IPv4)in_port_t       sin_port;     // 端口号(网络字节序)struct in_addr  sin_addr;     // IP地址(网络字节序)unsigned char   sin_zero[8];  // 填充字段,必须为0(与sockaddr兼容)
};struct in_addr {in_addr_t s_addr;  // 32位IPv4地址(网络字节序)
};

通用地址结构sockaddr(长度固定,用于函数参数统一):

struct sockaddr {sa_family_t sa_family;  // 地址族char        sa_data[14]; // 地址数据(含端口+IP,长度可变)
};

使用时需将sockaddr_in*强制转换为sockaddr*传给函数(如bindconnect)。

2. 字节序转换

网络中数据传输必须使用网络字节序(大端字节序),而主机可能是大端或小端(取决于CPU),因此需通过函数转换:

  • htons():主机字节序 → 网络字节序(16位,用于端口号)。
  • htonl():主机字节序 → 网络字节序(32位,用于IP地址)。
  • ntohs():网络字节序 → 主机字节序(16位)。
  • ntohl():网络字节序 → 主机字节序(32位)。

示例:将端口号8080转换为网络字节序:

uint16_t port = htons(8080); // 关键:端口必须转换,否则可能解析错误
3. IP地址转换

需将“点分十进制字符串”(如"192.168.1.1")与32位整数(网络字节序)互转:

  • 推荐使用兼容IPv4/IPv6的函数
    • inet_pton():字符串 → 网络字节序整数(presentation → network)。
    • inet_ntop():网络字节序整数 → 字符串(network → presentation)。

示例

struct sockaddr_in addr;
// 字符串IP → 网络字节序
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr); 
// 网络字节序 → 字符串
char ip_str[INET_ADDRSTRLEN]; // INET_ADDRSTRLEN:IPv4字符串最大长度(16)
inet_ntop(AF_INET, &addr.sin_addr, ip_str, INET_ADDRSTRLEN);

三、TCP编程(面向连接)

TCP通信需先建立连接(三次握手),再传输数据,最后释放连接(四次挥手)。

1. TCP服务器编程步骤

步骤

函数

作用

1. 创建socket

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

创建套接字描述符(文件句柄)

2. 绑定地址

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

将socket与IP+端口绑定

3. 监听连接

int listen(int sockfd, int backlog);

转为被动监听状态,等待客户端连接

4. 接受连接

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

阻塞等待并接受客户端连接,返回新socket

5. 收发数据

ssize_t recv(int sockfd, void* buf, size_t len, int flags); / ssize_t send(int sockfd, const void* buf, size_t len, int flags);

与客户端交换数据

6. 关闭连接

int close(int fd);

释放资源

函数参数说明

  • socket()domain=AF_INET(IPv4),type=SOCK_STREAM(TCP),protocol=0(默认协议)。
  • bind()addrlensockaddr_in的长度(sizeof(struct sockaddr_in))。
  • listen()backlog为等待队列最大长度(超过则拒绝新连接)。
  • accept()addr用于存储客户端地址,addrlen需传入地址长度的指针(入参为缓冲区大小,出参为实际长度)。
  • recv()/send()flags=0为默认模式(阻塞);recv返回接收字节数(0表示对方关闭,-1表示错误);send返回发送字节数(-1表示错误)。
2. TCP客户端编程步骤

步骤

函数

作用

1. 创建socket

socket()

同服务器

2. 连接服务器

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

与服务器建立TCP连接

3. 收发数据

recv()/send()

同服务器

4. 关闭连接

close()

同服务器

3. TCP实战:回显服务器与客户端   (linux)

服务器代码(tcp_server.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#define PORT 8080
#define BUF_SIZE 1024int main() {// 1. 创建TCP socketint listen_fd = socket(AF_INET, SOCK_STREAM, 0);if (listen_fd == -1) {perror("socket failed");exit(EXIT_FAILURE);}// 2. 绑定地址(IP+端口)struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = INADDR_ANY; // 绑定所有本地IPserv_addr.sin_port = htons(PORT);       // 端口转换为网络字节序if (bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {perror("bind failed");close(listen_fd);exit(EXIT_FAILURE);}// 3. 监听连接(等待队列长度为5)if (listen(listen_fd, 5) == -1) {perror("listen failed");close(listen_fd);exit(EXIT_FAILURE);}printf("Server listening on port %d...\n", PORT);// 4. 接受客户端连接(循环处理单客户端,实际需并发)struct sockaddr_in client_addr;socklen_t client_len = sizeof(client_addr);int conn_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);if (conn_fd == -1) {perror("accept failed");close(listen_fd);exit(EXIT_FAILURE);}// 打印客户端信息char client_ip[INET_ADDRSTRLEN];inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);printf("Accepted connection from %s:%d\n", client_ip, ntohs(client_addr.sin_port));// 5. 回显数据(接收后原样返回)char buf[BUF_SIZE];ssize_t n;while ((n = recv(conn_fd, buf, BUF_SIZE-1, 0)) > 0) {buf[n] = '\0'; // 确保字符串结束printf("Received: %s", buf);send(conn_fd, buf, n, 0); // 回显}if (n == -1) perror("recv failed");printf("Client disconnected\n");// 6. 关闭连接close(conn_fd);close(listen_fd);return 0;
}

客户端代码(tcp_client.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#define PORT 8080
#define BUF_SIZE 1024int main() {// 1. 创建TCP socketint sock_fd = socket(AF_INET, SOCK_STREAM, 0);if (sock_fd == -1) {perror("socket failed");exit(EXIT_FAILURE);}// 2. 连接服务器struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(PORT);// 转换服务器IP(此处为本地回环地址)if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {perror("invalid address");close(sock_fd);exit(EXIT_FAILURE);}if (connect(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {perror("connect failed");close(sock_fd);exit(EXIT_FAILURE);}printf("Connected to server\n");// 3. 发送数据并接收回显char buf[BUF_SIZE];while (1) {printf("Enter message (q to quit): ");fgets(buf, BUF_SIZE, stdin); // 从键盘输入if (buf[0] == 'q' && (buf[1] == '\n' || buf[1] == '\0')) break;send(sock_fd, buf, strlen(buf), 0);ssize_t n = recv(sock_fd, buf, BUF_SIZE-1, 0);if (n <= 0) {perror("recv failed");break;}buf[n] = '\0';printf("Echo: %s", buf);}// 4. 关闭连接close(sock_fd);return 0;
}

四、UDP编程(无连接)

UDP无需建立连接,直接发送“数据报”,适用于实时性要求高的场景(如视频、游戏)。

1. UDP编程步骤(服务器与客户端)

UDP服务器与客户端流程更简单,核心差异在于收发数据时需指定对方地址

角色

步骤(核心函数)

服务器

1. 创建socket(socket(AF_INET, SOCK_DGRAM, 0))2. 绑定地址(bind())3. 接收数据(recvfrom())4. 发送数据(sendto())5. 关闭(close())

客户端

1. 创建socket2. 直接sendto()发送数据(需指定服务器地址)3. recvfrom()接收数据4. 关闭

2. 核心函数:recvfrom()sendto()
代码语言:javascript
代码运行次数:0
运行
AI代码解释
 
// 接收数据(同时获取发送方地址)
ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags,struct sockaddr* src_addr, socklen_t* addrlen);// 发送数据(需指定接收方地址)
ssize_t sendto(int sockfd, const void* buf, size_t len, int flags,const struct sockaddr* dest_addr, socklen_t addrlen);
  • 参数与recv/send类似,多了src_addr(接收方地址)和dest_addr(发送目标地址)。
3. UDP实战:回显服务器与客户端

服务器代码(udp_server.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#define PORT 8080
#define BUF_SIZE 1024int main() {// 1. 创建UDP socketint sock_fd = socket(AF_INET, SOCK_DGRAM, 0);if (sock_fd == -1) {perror("socket failed");exit(EXIT_FAILURE);}// 2. 绑定地址struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = INADDR_ANY;serv_addr.sin_port = htons(PORT);if (bind(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {perror("bind failed");close(sock_fd);exit(EXIT_FAILURE);}printf("UDP server listening on port %d...\n", PORT);// 3. 接收并回显数据char buf[BUF_SIZE];struct sockaddr_in client_addr;socklen_t client_len = sizeof(client_addr);while (1) {// 接收客户端数据ssize_t n = recvfrom(sock_fd, buf, BUF_SIZE-1, 0,(struct sockaddr*)&client_addr, &client_len);if (n == -1) {perror("recvfrom failed");continue;}buf[n] = '\0';// 打印客户端信息char client_ip[INET_ADDRSTRLEN];inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);printf("Received from %s:%d: %s", client_ip, ntohs(client_addr.sin_port), buf);// 回显数据sendto(sock_fd, buf, n, 0, (struct sockaddr*)&client_addr, client_len);}// 4. 关闭(实际需信号处理退出)close(sock_fd);return 0;
}

客户端代码(udp_client.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#define PORT 8080
#define BUF_SIZE 1024int main() {// 1. 创建UDP socketint sock_fd = socket(AF_INET, SOCK_DGRAM, 0);if (sock_fd == -1) {perror("socket failed");exit(EXIT_FAILURE);}// 服务器地址struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(PORT);inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);socklen_t serv_len = sizeof(serv_addr);// 2. 发送数据并接收回显char buf[BUF_SIZE];while (1) {printf("Enter message (q to quit): ");fgets(buf, BUF_SIZE, stdin);if (buf[0] == 'q' && (buf[1] == '\n' || buf[1] == '\0')) break;// 发送到服务器sendto(sock_fd, buf, strlen(buf), 0, (struct sockaddr*)&serv_addr, serv_len);// 接收回显ssize_t n = recvfrom(sock_fd, buf, BUF_SIZE-1, 0, NULL, NULL);if (n == -1) {perror("recvfrom failed");break;}buf[n] = '\0';printf("Echo: %s", buf);}// 3. 关闭close(sock_fd);return 0;
}

Windows系统的C语言网络编程示例

使用Windows Sockets API(winsock2)实现TCP和UDP通信。代码结构与Linux版本类似,但需注意以下差异:

  1. 使用WSAStartup()初始化套接字库
  2. 使用WSACleanup()清理资源
  3. 使用closesocket()替代close()
  4. 使用WSAGetLastError()获取错误码
  5. 头文件和库文件不同(需链接ws2_32.lib

Windows TCP 回显服务器与客户端

服务器代码(tcp_server_win.c)

#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#pragma comment(lib, "ws2_32.lib")  // 链接winsock库#define PORT 8080
#define BUF_SIZE 1024int main() {WSADATA wsaData;SOCKET listenSocket = INVALID_SOCKET;SOCKET clientSocket = INVALID_SOCKET;struct sockaddr_in serverAddr, clientAddr;char recvBuf[BUF_SIZE];int iResult;int clientAddrLen = sizeof(clientAddr);// 1. 初始化 WinsockiResult = WSAStartup(MAKEWORD(2, 2), &wsaData);if (iResult != 0) {printf("WSAStartup failed: %d\n", iResult);return 1;}// 2. 创建监听套接字listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (listenSocket == INVALID_SOCKET) {printf("socket failed: %d\n", WSAGetLastError());WSACleanup();return 1;}// 3. 配置服务器地址memset(&serverAddr, 0, sizeof(serverAddr));serverAddr.sin_family = AF_INET;serverAddr.sin_addr.s_addr = INADDR_ANY;serverAddr.sin_port = htons(PORT);// 4. 绑定套接字iResult = bind(listenSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr));if (iResult == SOCKET_ERROR) {printf("bind failed: %d\n", WSAGetLastError());closesocket(listenSocket);WSACleanup();return 1;}// 5. 监听连接iResult = listen(listenSocket, 5);if (iResult == SOCKET_ERROR) {printf("listen failed: %d\n", WSAGetLastError());closesocket(listenSocket);WSACleanup();return 1;}printf("Server listening on port %d...\n", PORT);// 6. 接受客户端连接clientSocket = accept(listenSocket, (struct sockaddr*)&clientAddr, &clientAddrLen);if (clientSocket == INVALID_SOCKET) {printf("accept failed: %d\n", WSAGetLastError());closesocket(listenSocket);WSACleanup();return 1;}// 打印客户端信息char client_ip[INET_ADDRSTRLEN];inet_ntop(AF_INET, &clientAddr.sin_addr, client_ip, INET_ADDRSTRLEN);printf("Accepted connection from %s:%d\n", client_ip, ntohs(clientAddr.sin_port));// 7. 接收并回显数据while (1) {iResult = recv(clientSocket, recvBuf, BUF_SIZE - 1, 0);if (iResult > 0) {recvBuf[iResult] = '\0';printf("Received: %s", recvBuf);iResult = send(clientSocket, recvBuf, iResult, 0);if (iResult == SOCKET_ERROR) {printf("send failed: %d\n", WSAGetLastError());break;}} else if (iResult == 0) {printf("Connection closed\n");break;} else {printf("recv failed: %d\n", WSAGetLastError());break;}}// 8. 清理资源closesocket(clientSocket);closesocket(listenSocket);WSACleanup();return 0;
}

客户端代码(tcp_client_win.c)

#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#pragma comment(lib, "ws2_32.lib")  // 链接winsock库#define PORT 8080
#define BUF_SIZE 1024int main() {WSADATA wsaData;SOCKET connectSocket = INVALID_SOCKET;struct sockaddr_in serverAddr;char sendBuf[BUF_SIZE];char recvBuf[BUF_SIZE];int iResult;// 1. 初始化 WinsockiResult = WSAStartup(MAKEWORD(2, 2), &wsaData);if (iResult != 0) {printf("WSAStartup failed: %d\n", iResult);return 1;}// 2. 创建套接字connectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (connectSocket == INVALID_SOCKET) {printf("socket failed: %d\n", WSAGetLastError());WSACleanup();return 1;}// 3. 配置服务器地址memset(&serverAddr, 0, sizeof(serverAddr));serverAddr.sin_family = AF_INET;serverAddr.sin_port = htons(PORT);inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);// 4. 连接服务器iResult = connect(connectSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr));if (iResult == SOCKET_ERROR) {printf("connect failed: %d\n", WSAGetLastError());closesocket(connectSocket);WSACleanup();return 1;}printf("Connected to server\n");// 5. 发送和接收数据while (1) {printf("Enter message (q to quit): ");fgets(sendBuf, BUF_SIZE, stdin);if (sendBuf[0] == 'q' && (sendBuf[1] == '\n' || sendBuf[1] == '\0'))break;iResult = send(connectSocket, sendBuf, strlen(sendBuf), 0);if (iResult == SOCKET_ERROR) {printf("send failed: %d\n", WSAGetLastError());break;}iResult = recv(connectSocket, recvBuf, BUF_SIZE - 1, 0);if (iResult > 0) {recvBuf[iResult] = '\0';printf("Echo: %s", recvBuf);} else if (iResult == 0) {printf("Connection closed by server\n");break;} else {printf("recv failed: %d\n", WSAGetLastError());break;}}// 6. 清理资源closesocket(connectSocket);WSACleanup();return 0;
}

Windows UDP 回显服务器与客户端

服务器代码(udp_server_win.c)

#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#pragma comment(lib, "ws2_32.lib")  // 链接winsock库#define PORT 8080
#define BUF_SIZE 1024int main() {WSADATA wsaData;SOCKET sock = INVALID_SOCKET;struct sockaddr_in serverAddr, clientAddr;char recvBuf[BUF_SIZE];int iResult;int clientAddrLen = sizeof(clientAddr);// 1. 初始化 WinsockiResult = WSAStartup(MAKEWORD(2, 2), &wsaData);if (iResult != 0) {printf("WSAStartup failed: %d\n", iResult);return 1;}// 2. 创建套接字sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);if (sock == INVALID_SOCKET) {printf("socket failed: %d\n", WSAGetLastError());WSACleanup();return 1;}// 3. 配置服务器地址memset(&serverAddr, 0, sizeof(serverAddr));serverAddr.sin_family = AF_INET;serverAddr.sin_addr.s_addr = INADDR_ANY;serverAddr.sin_port = htons(PORT);// 4. 绑定套接字iResult = bind(sock, (struct sockaddr*)&serverAddr, sizeof(serverAddr));if (iResult == SOCKET_ERROR) {printf("bind failed: %d\n", WSAGetLastError());closesocket(sock);WSACleanup();return 1;}printf("UDP server listening on port %d...\n", PORT);// 5. 接收并回显数据while (1) {iResult = recvfrom(sock, recvBuf, BUF_SIZE - 1, 0,(struct sockaddr*)&clientAddr, &clientAddrLen);if (iResult > 0) {recvBuf[iResult] = '\0';// 打印客户端信息char client_ip[INET_ADDRSTRLEN];inet_ntop(AF_INET, &clientAddr.sin_addr, client_ip, INET_ADDRSTRLEN);printf("Received from %s:%d: %s", client_ip, ntohs(clientAddr.sin_port), recvBuf);// 回显数据iResult = sendto(sock, recvBuf, iResult, 0,(struct sockaddr*)&clientAddr, clientAddrLen);if (iResult == SOCKET_ERROR) {printf("sendto failed: %d\n", WSAGetLastError());}} else {printf("recvfrom failed: %d\n", WSAGetLastError());}}// 6. 清理资源closesocket(sock);WSACleanup();return 0;
}

客户端代码(udp_client_win.c)

include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#pragma comment(lib, "ws2_32.lib")  // 链接winsock库#define PORT 8080
#define BUF_SIZE 1024int main() {WSADATA wsaData;SOCKET sock = INVALID_SOCKET;struct sockaddr_in serverAddr;char sendBuf[BUF_SIZE];char recvBuf[BUF_SIZE];int iResult;int serverAddrLen = sizeof(serverAddr);// 1. 初始化 WinsockiResult = WSAStartup(MAKEWORD(2, 2), &wsaData);if (iResult != 0) {printf("WSAStartup failed: %d\n", iResult);return 1;}// 2. 创建套接字sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);if (sock == INVALID_SOCKET) {printf("socket failed: %d\n", WSAGetLastError());WSACleanup();return 1;}// 3. 配置服务器地址memset(&serverAddr, 0, sizeof(serverAddr));serverAddr.sin_family = AF_INET;serverAddr.sin_port = htons(PORT);inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);printf("UDP client ready\n");// 4. 发送和接收数据while (1) {printf("Enter message (q to quit): ");fgets(sendBuf, BUF_SIZE, stdin);if (sendBuf[0] == 'q' && (sendBuf[1] == '\n' || sendBuf[1] == '\0'))break;// 发送数据iResult = sendto(sock, sendBuf, strlen(sendBuf), 0,(struct sockaddr*)&serverAddr, serverAddrLen);if (iResult == SOCKET_ERROR) {printf("sendto failed: %d\n", WSAGetLastError());continue;}// 接收响应iResult = recvfrom(sock, recvBuf, BUF_SIZE - 1, 0, NULL, NULL);if (iResult > 0) {recvBuf[iResult] = '\0';printf("Echo: %s", recvBuf);} else {printf("recvfrom failed: %d\n", WSAGetLastError());}}// 5. 清理资源closesocket(sock);WSACleanup();return 0;
}

编译和运行说明

编译命令(使用MinGW或Visual Studio命令行工具):

# 编译TCP服务器
gcc tcp_server_win.c -o tcp_server_win -lws2_32# 编译TCP客户端
gcc tcp_client_win.c -o tcp_client_win -lws2_32

运行步骤
  • 先启动服务器程序(TCP/UDP)
  • 再启动客户端程序(TCP/UDP)
  • 在客户端输入消息,按回车发送,输入q退出

注意事项

  • 确保防火墙允许程序访问网络
  • 若端口被占用,可修改代码中的PORT
  • Windows换行符为\r\n,可能影响某些应用场景(如HTTP协议)

五、高级主题

1. 并发处理(TCP服务器)

单个TCP服务器默认只能处理一个客户端,需通过以下方式实现并发:

  • 多进程fork()子进程处理新连接(父进程继续accept)。
  • 多线程pthread_create()创建线程处理连接。
  • IO多路复用select()/poll()/epoll()(Linux)同时监控多个socket,高效处理高并发。
2. IO多路复用(以select()为例)

select()可同时监控多个文件描述符(如socket),当有数据可读/可写时通知程序:

fd_set readfds;          // 可读文件描述符集合
FD_ZERO(&readfds);       // 初始化
FD_SET(listen_fd, &readfds); // 添加监听socket
int max_fd = listen_fd;while (1) {fd_set tmp = readfds; // 每次需重置(select会修改集合)// 阻塞等待,超时返回0int activity = select(max_fd + 1, &tmp, NULL, NULL, NULL);if (activity == -1) { perror("select"); break; }// 检查监听socket是否有新连接if (FD_ISSET(listen_fd, &tmp)) {// accept新连接,添加到readfds}// 检查已连接socket是否有数据for (int i = 0; i <= max_fd; i++) {if (FD_ISSET(i, &tmp) && i != listen_fd) {// recv数据并处理}}
}
3. 错误处理与调试

所有Socket函数需检查返回值(-1表示错误),用perror()打印错误信息。

端口占用:bind失败可能是端口被占用,可通过sudo lsof -i :端口号查看进程并杀死,或设置SO_REUSEADDR选项允许端口复用:

int opt = 1;
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

六、总结

C语言网络编程的核心是Socket接口,需重点掌握:

  • TCP与UDP的差异(连接 vs 无连接,可靠 vs 高效)。
  • 地址结构(sockaddr_in)、字节序转换(htons等)、IP转换(inet_pton)。
  • 核心函数的参数与返回值(尤其是错误处理)。

参考:https://blog.csdn.net/2301_79536841/article/details/151398966

https://cloud.tencent.com/developer/article/2542482

 

相关文章:

windows与linux环境下网络编程

windows与linux环境进行网络编程,使用的库是不一样的, 下面主要说一下windows环境下的网络编程; 网络编程从大的方面说就是对信息的发送到接收,中间传输为物理线路的作用。网络编程最主要的工作就是在发送端把信息通过规定好的协议进行组装包,在接收端按照规定好的协议把包…...

在飞牛系统中通过docker形式部署Nginx proxy manager

在飞牛系统中通过docker形式部署Nginx proxy manager本文仅用用于记录。 目标: 分别通过以飞牛的图形化界面和docker compose实现以docker的方式部署Nginx proxy manager。 首先,管理员应当新建2个文件夹用于持久化数据存储(就是你想要留着的数据找个地方放好) 1.data 存放…...

Es索引同步异步Canal解耦方案

Es索引同步异步Canal解耦方案 首先传统的Es索引同步耦合性太高需要使用业务逻辑来同步,一旦在同步的时候服务器出现异常就用可能同步失败导致影响到其他业务,这里使用的是Canal中间件来实现异步同步索引。 简述: ​ 这里以电商项目为例,当需要添加,修改或删除商品时为了电…...

在Ubuntu上配置phpMyAdmin和WordPress环境

在Ubuntu中配置phpMyAdmin和WordPress环境需要执行一系列的步骤,包含安装LAMP(Linux, Apache, MySQL, PHP)栈,配置数据库,然后安装phpMyAdmin和WordPress。 下面是具体的配置步骤分解: 安装LAMP环境更新系统首先更新你的Ubuntu系统: sudo apt update sudo apt upgrade安装…...

“四人过河”经典问题

一、什么是“四人过河”经典问题 最早版本见于 MBA/奥数/信息学趣题: N 个人(通常 N=4)要从左岸到右岸,只有一条小船,容量至多 2 人;船划行时间 = 船上所有人中最大的那一项; 船不能空驶,每次必须有人把船划回来; 问:让所有人到达对岸的最短总时间是多少?二、通用数…...

完整教程:C#语言入门详解(18)传值、输出、引用、数组、具名、可选参数、扩展方法

完整教程:C#语言入门详解(18)传值、输出、引用、数组、具名、可选参数、扩展方法pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", &q…...

DevOps On Kubernetes

OSChina在深圳举办的”源创会年终盛典”上,华为云容器服务技术总监发表了名为《DevOps On Kubernetes》的主题演讲,演讲就如何将DevOps理念与容器技术相结合,实现容器化场景下的快速交付进行介绍,并重点介绍了华为云容器服务提供的持续交付工具——容器交付流水线(Contain…...

深耕Linux系统的道与术

- [著名系统内核专家张银奎老师Boolan直播间开讲:《深耕Linux系统的道与器》\_哔哩哔哩\_bilibili](https://www.bilibili.com/video/BV1pK42187EY/?vd_source=589169a942bb977a9dbcdd5f0ea6318b) - [张银奎 | 2024全球C++及系统软件技术大会](https://www.cpp-summit.org/sp…...

Debugging via Intel DCI 小蓝盒

- [PowerPoint 演示文稿](http://advdbg.org/gdk/download/20200722-DCI_DBG_FullStack.pdf)- [使用DCI单步调试Intel CPU,调试运行在其上的UEFI代码 - 程序员大本营](https://www.pianshen.com/article/4402459283/)- [[讨论]使用Intel DCI调试12代笔记本CPU来解决安装黑苹果…...

我做了个 AI 文档阅读神器,免费开源!

为了帮助大家免受文档的折磨,我用 AI 开发了个 AI 文档助手网站,可以帮你快速读懂各种复杂的文档、还帮你管理文档。大家好,我是程序员鱼皮。开学季到了,想必很多朋友要开始收集和阅读论文,像我自己学习新技术知识也会去阅读文档,我深知阅读文档的痛苦。明明每个词拆开都…...

20250913 P11503 [NordicOI 2018] Nordic Camping

P11503 [NordicOI 2018] Nordic Camping 二分 + DS 妙题 思路 首先我们可以发现。若有一个点 \((x, y)\),则我们可以通过二分求出以 \((x, y)\) 为左上角的最大空正方形的边长(记为 \(b[i][j]\)),check 就是判断以 \((x, y)\) 为左上角的边长为 \(mid\) 的正方形是否为空,…...

Dify实战训练营(基础班)(全免费值得收藏)

Dify实战训练营(基础班)(全免费值得收藏) Dify全链路实战【图文】 【01】【2025年最新版】Dify1.5.0升级部署实战指南 【02】Dify 代码执行节点实战:手把手解锁自定义业务开发能力 【03】Dify全链路实战 :TTS 实操,三步搭建语音化工作流 【04】【2025 年最新版】Dify 实…...

C 语言的历史和版本

C 语言的历史 1972年,贝尔实验室的 Dennis Ritchie 在开发 UNIX 操作系统时设计了 C 语言。C 语言是在 B 语言的基础上设计的。 C 语言的版本 K&R C K&R C 也叫 经典 C,以《The C Programming Language》【Brain Kernighan,Dennis Ritchie】(1987)(第 1 版)作为…...

PostgreSQL 上的向量搜索实践

本文整理自 IvorySQL 2025 生态大会暨 PostgreSQL 高峰论坛的演讲分享,演讲嘉宾:高策,TensorChord CEO。引言 本文主要分享如何在 PostgreSQL 上进行向量的搜索。 PostgreSQL 有非常多的 Extension,我们可以通过 Extension 的方式去扩展 PostgreSQL 能够支持的数据类型。这…...

【数据结构——图与邻接矩阵】 - 实践

【数据结构——图与邻接矩阵】 - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !important…...

(读书笔记)平衡掌控者

这本书比较短,主要是国内某数值设计师的MMORpg的设计经验。 虽然大部分是mmo制作的细节,可以不用太深挖,但还是有不少“游戏共性”可以参考和学习的。 主要是第2章和第4章的内容。 下面记录一些关键点并做了一些拓展。 ---------------------------------------------------…...

带头结点的单链表删除指定位置结点

1. 功能概述 bool ListDelete(LinkList &L, int i, ElemType &e) 函数的功能是:在带头结点的单链表 L 中,删除第 i 个位置的结点,并将被删除结点的数据通过引用参数 e 带回给调用者。函数名: ListDelete,清晰表达了其功能。 返回值: bool 类型。true 表示删除成功,…...

《文字、语言与数字的奇妙联结》读后感,大公司内部编码规范,本学期编码遵守规范

《文字、语言与数字的奇妙联结》读后感 读到 “文字和语言与数学,从产生起原本就有相通性,虽一度分道扬镳,最终仍能走到一起” 时,我被这种跨领域的深层联结深深触动。 最初,文字、语言与数学或许本是 “同根生”—— 它们都发源于人类认知世界、传递经验的需求。远古时期…...

[HTTP/Spring] RestTemplate : Spring的HTTP网络请求框架

0 序java应用开发中,使用http连接,访问第三方HTTP接口,通常使用的HTTP连接请求工具为HttpClient和OKHttp。这两种HTTP连接请求工具,使用起来比较复杂,新手容易出问题。如果使用spring框架,可以使用restTemplate来进行http连接请求。restTemplate默认的连接方式是java中的…...

深入解析:Linux使用-MySQL的使用

深入解析:Linux使用-MySQL的使用pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !important; fo…...

博客园-我的博客-的皮肤更换

皮肤地址 GShang写的博客皮肤 最近用的这个博主写的 cnbook ,强力安利!...

Apache Commons Math3 使用指南:强大的Java数学库 - 教程

Apache Commons Math3 使用指南:强大的Java数学库 - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", m…...

HarmonyOS图形处理:Canvas绘制与动画开发实战

本文将全面介绍HarmonyOS 5中Canvas组件的使用方法和动画开发技巧,通过详细的代码示例和最佳实践,帮助您掌握图形绘制和动态效果实现的核心技能。1. Canvas组件基础与核心API Canvas是HarmonyOS中用于2D图形绘制的重要组件,提供了丰富的绘图接口和灵活的动画支持。 1.1 Canv…...

应用的微服务化-容器化-CI/CD

第一阶段:需要对应用按照云原生架构进行应用的微服务化改造,容器化改造,以及对接CI/CD平台,使得应用具备云原生的特点 <1.> 微服务注册配置中心 业务系统在进行微服务化改造的过程中,会涉及微服务的注册,以便于被其他所依赖的上游服务发现,分别是Naco,Eureka和Zo…...

[转发和重定向] 的核心定义

重定向和转发 不废话: “转发” 的核心定义: 服务端内部主导跳转、客户端无感知(仅 1 次请求)、浏览器 URL 不改变,与传统 Web 开发中 “转发” 的本质逻辑完全一致,只是实现载体(Nginx 路由层 vs 上层业务框架)不同,不影响其 “转发” 的属性归属。 “重定向”核心定…...

script setup 在 Vue 3 中的核心作用及具体实现方式

​​一、核心作用​​ 1. ​​简化 Composition API 语法​​​​自动暴露顶层变量​​:无需手动通过 return 暴露属性或方法,直接声明即可被模板使用。 <script setup> const count = ref(0); // 自动暴露给模板 </script> <template><p>{{ count }…...

0voice-1.4.1-cmake

cmake介绍...

test test test

this test string...

容器化改造基本原理

虚拟化和容器化是项目云化不可避免的两个问题。虚拟化由于是纯平台操作,一个运行于linux操作系统的项目几乎不需要做任何改造就可以支持虚拟化。而项目如果要支持容器化则需要做许多细致的改造工作。容器化相对于虚拟化的优势也相当明显,运行于裸机性能高,秒级启停容器,更不…...

Blogroll 友链

Blogroll 友链愿我如星君如月,夜夜流光相皎洁。...

Java 字节码与 ASM 框架实战解析

Java 虚拟机(JVM)以字节码(Bytecode)为基础执行所有 Java 程序。对于希望深入理解 Java 底层运行机制,或开发自定义编译器、性能探测器、动态增强框架(如代理、AOP)的开发者来说,掌握 Java 字节码结构与 ASM 等字节码操作工具极为重要。 本篇文章将深入解析 Java 字节码…...

计算机大数据毕业设计选题:基于Spark+hadoop的全球香水市场趋势分析系统 - 详解

计算机大数据毕业设计选题:基于Spark+hadoop的全球香水市场趋势分析系统 - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "…...

Dos的常用命令

常用的Dos命令 #盘符切换 #查看当前目录下的所有文件 dir #切换目录 cd change directory cd..返回上一级 #清理屏幕 cls(clear screen) #退出终端 exit #查看电脑的ip ipconfig #打开应用calc(计算器)mspaint(画图)notepad(笔记)#ping 命令 ping www.baidu.com#文件操作…...

持续集成自动化CI/CD

背景Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。持续集成(Continuous integration)是一种软件开发实践,即团队开…...

Lightroom Classic 2025(LRC 2025)安装教程(附直接安装包下载)+入门操作指南

一、LRC 2025下载及软件介绍 Lightroom Classic 2025 v14.0.1 是 Adobe 旗下专业图片后期处理软件,主打RAW 格式图片编辑,专为摄影师、摄影爱好者及数码摄影 / 图形设计从业者设计,提供从照片导入、整理到编辑、打印的全套服务。支持各类相机 RAW 文件解析,能高效完成照片调…...

2025/09/14 【二叉树11】完全二叉树的节点个数

222. 完全二叉树的节点个数 - 力扣(LeetCode)...

8888

jjjj...

接口限流代码 - 实践

接口限流代码 - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !important; font-size: 14p…...

OutGuess 安装与问题排查指南(Kali Linux 环境)

OutGuess 安装与问题排查指南(Kali Linux 环境) 本文档基于实际操作流程,整理了 OutGuess 工具从下载、编译安装到常见问题解决的完整步骤,适用于 CTF 隐写术场景下的工具配置需求。 一、工具简介 OutGuess 是经典隐写术工具,主要用于在 JPEG/PPM 图像 中隐藏 / 提取数据,…...

拓展操作码举例

拓展操作码举例 指令字长16位,每个地址码占4位: 那么如果要表示三地址操作码有16条 前四位表示操作码op,后面有3个4位长的地址字段A1,A2,A3 三地址指令 至少需要将1111留作扩展操作码之用,因此三地址指令只能有2^4 -1=15条 二地址指令 至少将 1111 1111 留作扩展操作码之…...

TryHackMe | Cicada-3301 Vol:1

TryHackMe | Cicada-3301 Vol:1 一、分析音频软件 先把文件下载到本地使用unzip解压缩,得到一个音频文件和一个图片我们这边先查看一下图片里面是什么内容大致意思:你好。我们正在寻找高智商的人。为了找到他们,我们设计了一个测试。 这张图片里藏有一条信息。找到它,它会指…...

[MCP][01]简介与概念

MCP简介与概念简介 MCP(全称为Model Context Protocol,模型上下文协议)是一种面向大模型交互过程的通用上下文协议标准。其核心目标在于为模型构建一个结构化、可控、可扩展的语义执行环境,使语言模型能够在统一的上下文管理体系下进行任务调度、工具调用、资源协作与状态保…...

完整教程:Word添加图/表题注

完整教程:Word添加图/表题注pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !important; font-s…...

CF819B Mister B and PR Shifts

CF819B Mister B and PR Shifts 题目描述 Some time ago Mister B detected a strange signal from the space, which he started to study. After some transformation the signal turned out to be a permutation $ p $ of length $ n $ or its cyclic shift. For the furthe…...

第一次自我介绍

大家好,我是李嘉倩,核心兴趣是《和平精英》《第五人格》这类竞技游戏,更擅长把游戏变成 “数据收集与分析” 的实践场,这也是我的核心优势。 为提升游戏策略,我坚持1 年半做了两件事:数据收集:用 Excel 记录《和平精英》200 + 局的落地点物资刷新率、对战淘汰 / 成盒原因…...

在Linux环境部署Flask应用并启用SSL/TLS安全协议

部署Flask应用通常涉及以下几个步骤:准备应用程序、选择合适的WSGI服务器、配置Web服务器代理、申请SSL证书以及配置SSL。 准备Flask应用 首先,你需要在Linux服务器上安装必要的软件,包括:Python和pip(Python包管理器) 虚拟环境(virtualenv或conda) Flask框架和依赖库使…...

0127_责任链模式(Chain of Responsibility)

责任链模式(Chain of Responsibility) 意图 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。 UML 图优点降低耦合度:请求发送者不需要知道哪个对象处理请求 动态组合:可以…...

洛枫娜娜米讨厌数学……?

Xuan_qwq 好闪,拜谢 Xuan_qwq!插板 Catalan 数 应用一般有两种模型:折线型。 二叉树型。? CF1696E Placing Jinas 诈骗题。“最小值”纯诈骗,实际上操作次数和操作顺序没半毛钱关系。 每个点上的操作次数就是从 $(0, 0)$ 到这个点的路径条数,这是容易理解的,一条路径带来…...

Spatial 语言核心概念简介

我们来详细解析一下斯坦福大学的 Spatial 语言。这是一个非常专业且强大的领域特定语言(DSL),用于高性能硬件(如FPGA或ASIC)生成。 一、Spatial 语言核心概念简介 Spatial 是一个嵌入在 Scala 内部的 DSL(Domain-Specific Language)。它的根本目标是让软件工程师和算法专…...

Redis数据库的五类核心数据结构

Redis是一个开源的键值存储系统,支持多种类型的值模型,其中五种核心的数据结构是:String(字符串)、List(列表)、Set(集合)、Hash(哈希表)、和Sorted Set(有序集合)。下面将对每种数据结构进行详细解析。 1. String(字符串) String数据结构是最简单的数据类型,通…...