网络编程完结整理
UDP(User Datagram Protocol)
网络编程(一)UDP-CSDN博客
-
特点
无连接、数据报文传输、不保证可靠性、不保证顺序,适合轻量级、实时性要求高的场景(如语音、视频、局域网发现)。 -
编程要点
-
使用
socket(AF_INET, SOCK_DGRAM, 0)
创建。 -
sendto
/recvfrom
配合 sockaddr 结构,指定目标 IP/端口。 -
由于无连接,不需要三次握手,代码更简单,但要注意丢包和顺序错乱。
-
-
应用场景
实时数据传输、局域网发现、简单的状态广播。
2. 组播(Multicast)与广播(Broadcast)
网络编程(二)组播与广播-CSDN博客
-
广播(Broadcast)
-
数据发往局域网内所有设备(255.255.255.255 或子网广播地址)。
-
特点是无差别“喊话”,接收端只要在同一广播域就能收到。
-
缺点是会占用带宽,不适合大规模或跨路由使用。
-
-
组播(Multicast)
-
数据发往一个“组地址”(224.0.0.0 ~ 239.255.255.255),只有加入组的主机才会收到。
-
接收端需要调用
setsockopt
设置IP_ADD_MEMBERSHIP
加入组播组。 -
节省带宽,适合一对多场景。
-
-
应用场景
-
广播:ARP 请求、局域网内的发现。
-
组播:视频会议、股票行情推送、网络电视。
-
3. TCP(Transmission Control Protocol)
网络编程(三)TCP-CSDN博客
-
特点
面向连接、可靠传输、保证顺序、支持流量控制。适合需要可靠性的数据传输。 -
编程要点
-
创建 socket:
socket(AF_INET, SOCK_STREAM, 0)
。 -
服务端:
bind → listen → accept
,接收客户端连接。 -
客户端:
connect
发起三次握手。 -
数据传输:
send/recv
,注意 TCP 是“流”,可能存在粘包/拆包问题,需要自定义协议。
-
-
应用场景
聊天室、文件传输、Web 服务。
4. 原始套接字(Raw Socket)
网络编程(四)原始套接字-CSDN博客
-
特点
-
可以绕过 TCP/UDP 协议,直接操作网络层(IP)甚至链路层的数据包。
-
常用于网络嗅探、协议分析、安全测试。
-
-
编程要点
-
创建:
socket(AF_INET, SOCK_RAW, protocol)
。 -
通常需要 root/管理员权限。
-
用户需要自己构造 IP 头、TCP/UDP 头以及数据部分。
-
抓包工具(如 tcpdump、Wireshark)原理即是基于原始套接字。
-
-
应用场景
协议栈学习、自定义协议、IDS/IPS(入侵检测/防御系统)、网络扫描。
总结
-
UDP:轻量、无连接,适合实时性。
-
组播/广播:一对多通信的扩展方式;广播无差别喊话,组播“按兴趣订阅”。
-
TCP:面向连接、可靠性高,支撑绝大多数互联网应用。
-
原始套接字:让开发者直面协议头,适合做网络安全、协议研究。
这四个部分,像是从“普通通信”到“进阶一对多”,再到“可靠传输”,最后“深入内核”的一个学习链路。
相关函数总结:网络编程相关函数-CSDN博客
小作业整理
TCP服务器与小客户端的实现
共用头文件ChatMain.h
#ifndef _CHATMAIN_H
#define _CHATMAIN_H#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <unistd.h>#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>#define SERVER_IP_ADDR "192.168.119.53"//192.168.16.96
#define SERVER_PORT (8999)
#define SIZE (1024)
#define FDSIZE (10240)// 全局变量声明,使用 extern 表示在其他地方定义
extern int sockUserFd;
extern int sockServerFd;struct File
{char name[SIZE];char data[FDSIZE];size_t size;
};typedef struct message
{int messageType; // 0服务器消息,1用户消息,2文件消息char targetname[32]; char note[SIZE]; // 消息内容struct File file; // 文件
} MES;typedef struct ClientNode {pthread_t threadid; // 每个客户端线程IDint userfd; // 客户端套接字struct sockaddr_in userAddr; // 客户端地址信息char username[SIZE]; // 存储用户名struct ClientNode *next; // 链表指针int isFinished; // 标志位,标记线程是否完成
} CN;extern CN *head;
extern int clientCount;extern pthread_mutex_t Umutex;
extern pthread_mutex_t listMutex; extern int quitFlag;
extern int sockUserFd;#endif
服务器代码
服务器头文件ServerTask.h
#ifndef _SERVERTASK_H
#define _SERVERTASK_H#include "ChatMain.h"void *functionServer(void * argv);
void *functionUser(void * argv);
void *functionMonitor(void *argv);#endifSFunction.h
#ifndef _SFUNCTION_H
#define _SFUNCTION_H#include "ServerTask.h"void Node(pthread_t threadid, int userfd, struct sockaddr_in userAddr, const char *username);
void deleteNode(pthread_t threadid);
void sendOnlineUsers(int userfd);
void transmitUserMessage(int userfd, MES *URmes, const char *senderName);
void broadcastMessage(const char *message);
void broadcastOnlineUsers(void);#endif
实现代码ServerMain.c
#include "SFunction.h"int sockUserFd;
int sockServerFd;CN *head = NULL;
int clientCount = 0;pthread_mutex_t Umutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t listMutex = PTHREAD_MUTEX_INITIALIZER;int quitFlag = 0;int main(int argc, char const *argv[])
{sockServerFd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);if (-1 == sockServerFd){perror("socket failed");exit(EXIT_FAILURE);}struct sockaddr_in addr;socklen_t addrlen = sizeof(addr);memset(&addr, 0, addrlen);addr.sin_family = AF_INET;addr.sin_port = htons(SERVER_PORT);inet_pton(AF_INET, SERVER_IP_ADDR, &addr.sin_addr.s_addr);if (bind(sockServerFd, (const struct sockaddr *)&addr, addrlen)){perror("bind failed");close(sockServerFd);exit(EXIT_FAILURE);}if (listen(sockServerFd, 5)){perror("listen failed");close(sockServerFd);exit(EXIT_FAILURE);}pthread_t pthidMonitor;pthread_create(&pthidMonitor, NULL, functionMonitor, NULL); // 总回收pthread_t pthidServer;pthread_create(&pthidServer, NULL, functionServer, NULL);pthread_join(pthidServer, NULL);pthread_join(pthidMonitor, NULL);close(sockServerFd);return 0;
}ServerTask.c
#include "SFunction.h"void *functionUser(void *argv)
{intptr_t userfd = (intptr_t)argv;char myname[SIZE] = {0};pthread_mutex_lock(&listMutex);CN *t = head;while (t != NULL){if (t->userfd == userfd){strncpy(myname, t->username, SIZE - 1);break;}t = t->next;}pthread_mutex_unlock(&listMutex);MES URmes;while (1){memset(&URmes, 0, sizeof(MES));int bytesRead = read(userfd, &URmes, sizeof(MES));if (bytesRead <= 0){printf("用户 %s 已断开连接\n", myname);break;}if (URmes.messageType == 1){printf("[私聊] %s -> %s: %s\n", myname, URmes.targetname, URmes.note);}else if (URmes.messageType == 2){printf("[文件] %s -> %s: %s (大小: %zu bytes)\n",myname, URmes.targetname, URmes.file.name, URmes.file.size);}transmitUserMessage(userfd, &URmes, myname);}close(userfd);pthread_mutex_lock(&listMutex);CN *temp = head;while (temp != NULL){if (temp->threadid == pthread_self()){temp->isFinished = 1;break;}temp = temp->next;}pthread_mutex_unlock(&listMutex);char notice[SIZE];snprintf(notice, SIZE, "用户 %s 下线了", myname);broadcastMessage(notice);return NULL;
}void *functionServer(void *argv)
{while (1){struct sockaddr_in userAddr;socklen_t addrlen = sizeof(struct sockaddr);int userfd = accept(sockServerFd, (struct sockaddr *)&userAddr, &addrlen);if (userfd < 0){perror("accept failed");continue;}char username[SIZE] = {0};int n = read(userfd, username, SIZE);if (n <= 0){printf("接收用户名失败\n");close(userfd);continue;}pthread_t pthid;if (pthread_create(&pthid, NULL, functionUser, (void *)(intptr_t)userfd) != 0){perror("pthread_create failed");close(userfd);continue;}Node(pthid, userfd, userAddr, username);sendOnlineUsers(userfd);char notice[SIZE];snprintf(notice, SIZE, "用户 %s 上线了", username);broadcastMessage(notice);}return NULL;
}void *functionMonitor(void *argv)
{while (1){pthread_mutex_lock(&listMutex);CN *temp = head;CN *prev = NULL;while (temp != NULL){if (temp->isFinished == 1){pthread_join(temp->threadid, NULL);printf("回收线程 %lu\n", temp->threadid);if (prev == NULL){head = temp->next;}else{prev->next = temp->next;}CN *toFree = temp;temp = temp->next;free(toFree);clientCount--;}else{prev = temp;temp = temp->next;}}pthread_mutex_unlock(&listMutex);sleep(1);}return NULL;
}SFunction.c
#include "SFunction.h"void Node(pthread_t threadid, int userfd, struct sockaddr_in userAddr, const char *username)
{CN *newNode = (CN *)malloc(sizeof(CN));if (newNode == NULL){perror("malloc failed");return;}newNode->threadid = threadid;newNode->userfd = userfd;newNode->userAddr = userAddr;strncpy(newNode->username, username, SIZE);newNode->next = NULL;newNode->isFinished = 0;pthread_mutex_lock(&listMutex);if (head == NULL){head = newNode;}else{CN *temp = head;while (temp->next != NULL){temp = temp->next;}temp->next = newNode;}clientCount++;pthread_mutex_unlock(&listMutex);
}void deleteNode(pthread_t threadid)
{pthread_mutex_lock(&listMutex);CN *prev = NULL;CN *current = head;while (current != NULL){if (current->threadid == threadid){if (prev == NULL){head = current->next;}else{prev->next = current->next;}if (head == NULL){head = NULL;}free(current);clientCount--;break;}prev = current;current = current->next;}pthread_mutex_unlock(&listMutex);
}void transmitUserMessage(int userfd, MES *URmes, const char *senderName)
{CN *targetNode = NULL;pthread_mutex_lock(&listMutex);CN *temp = head;while (temp != NULL){if (strcmp(temp->username, URmes->targetname) == 0){targetNode = temp;break;}temp = temp->next;}pthread_mutex_unlock(&listMutex);if (targetNode != NULL){MES sendMsg;memset(&sendMsg, 0, sizeof(MES));sendMsg.messageType = URmes->messageType;strncpy(sendMsg.targetname, senderName, sizeof(sendMsg.targetname) - 1);if (URmes->messageType == 1){snprintf(sendMsg.note, SIZE, "%s: %s", senderName, URmes->note);int total = 0;int size = sizeof(MES);char *ptr = (char *)&sendMsg;while (total < size){int n = write(targetNode->userfd, ptr + total, size - total);if (n <= 0)break;total += n;}}else if (URmes->messageType == 2){sendMsg.file = URmes->file;snprintf(sendMsg.note, SIZE, "%s 发来一个文件: %s", senderName, URmes->file.name);int total = 0;int size = sizeof(MES);char *ptr = (char *)&sendMsg;while (total < size){int n = write(targetNode->userfd, ptr + total, size - total);if (n <= 0)break;total += n;}printf("文件已转发给 %s\n", URmes->targetname);}}else{MES serverMsg;memset(&serverMsg, 0, sizeof(MES));serverMsg.messageType = 0;snprintf(serverMsg.note, SIZE, "目标用户 %s 不在线或不存在。", URmes->targetname);int total = 0;int size = sizeof(MES);char *ptr = (char *)&serverMsg;while (total < size){int n = write(userfd, ptr + total, size - total);if (n <= 0)break;total += n;}}
}void sendOnlineUsers(int userfd)
{pthread_mutex_lock(&listMutex);CN *iter = head;MES mes;memset(&mes, 0, sizeof(MES));mes.messageType = 0; // 服务器消息while (iter != NULL){snprintf(mes.note, SIZE, "在线用户: %s", iter->username);write(userfd, &mes, sizeof(MES));iter = iter->next;}pthread_mutex_unlock(&listMutex);
}void broadcastMessage(const char *message)
{pthread_mutex_lock(&listMutex);CN *temp = head;while (temp != NULL){MES serverMsg;memset(&serverMsg, 0, sizeof(MES));serverMsg.messageType = 0; // 服务器消息strncpy(serverMsg.note, message, SIZE - 1);int total = 0;int size = sizeof(MES);char *ptr = (char *)&serverMsg;while (total < size){int n = write(temp->userfd, ptr + total, size - total);if (n <= 0){// 写失败跳过break;}total += n;}temp = temp->next;}pthread_mutex_unlock(&listMutex);
}
客户端代码
客户端头文件
UserTask.h
#ifndef _USERTASK_H
#define _USERTASK_H
#include "ChatMain.h"void *functionRead(void *argv);
void *functionWrite(void *argv);#endifUFunction.h
#ifndef _UFUNCTION_H
#define _UFUNCTION_H
#include "UserTask.h"void ShowMenu(void);
void messageSend(MES *UWmes);
void fileSend(MES *UWmes);
void cleanStdin(void);#endif
客户端代码UserMain.c
#include "UFunction.h"int sockUserFd;
pthread_mutex_t Umutex = PTHREAD_MUTEX_INITIALIZER;
int quitFlag = 0;int main(int argc, char const *argv[])
{sockUserFd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);if (-1 == sockUserFd){perror("socket failed");exit(EXIT_FAILURE);}struct sockaddr_in addr;socklen_t addrlen = sizeof(addr);memset(&addr, 0, addrlen);addr.sin_family = AF_INET;addr.sin_port = htons(SERVER_PORT);inet_pton(AF_INET, SERVER_IP_ADDR, &addr.sin_addr.s_addr);if (connect(sockUserFd, (const struct sockaddr *)&addr, addrlen)){perror("connect failed");close(sockUserFd);exit(EXIT_FAILURE);}char username[SIZE];printf("请输入用户名:");fgets(username, SIZE, stdin);username[strcspn(username, "\n")] = 0;write(sockUserFd, username, SIZE);pthread_t pthidRead, pthidWrite;pthread_create(&pthidRead, NULL, functionRead, NULL);pthread_create(&pthidWrite, NULL, functionWrite, NULL);pthread_join(pthidWrite, NULL); pthread_join(pthidRead, NULL); printf("客户端已退出。\n");return 0;
}UserTask.c
#include "UFunction.h"void *functionRead(void *argv)
{MES URmes;while (1){int total = 0;int size = sizeof(MES);char *ptr = (char *)&URmes;while (total < size){int n = read(sockUserFd, ptr + total, size - total);if (n <= 0){if (!quitFlag)printf("读取数据失败或连接已关闭\n");return NULL; }total += n;}if (URmes.messageType == 0) {printf("[服务器消息] %s\n", URmes.note);}else if (URmes.messageType == 1){printf("[私聊] %s: %s\n", URmes.targetname, URmes.note);}else if (URmes.messageType == 2){printf("[文件] 来自 %s 的文件: %s (大小: %zu bytes)\n",URmes.targetname, URmes.file.name, URmes.file.size);}}return NULL;
}void *functionWrite(void *argv)
{MES UWmes;char input[SIZE];int opt;while (1){memset(&UWmes, 0, sizeof(MES));ShowMenu();printf("请选择操作: ");fgets(input, SIZE, stdin);if (sscanf(input, "%d", &opt) != 1){printf("输入错误,请重新输入数字\n");continue;}switch (opt){case 1: // 发送消息messageSend(&UWmes);break;case 2: // 发送文件fileSend(&UWmes);break;case 3: // 退出printf("正在退出客户端...\n");quitFlag = 1;shutdown(sockUserFd, SHUT_RD); // 让 functionRead 中 read 返回 0return NULL; default:printf("没有这个选项\n");break;}}
}UFunction.c
#include "UFunction.h"void ShowMenu(void)
{printf("1.发送消息 ");printf("2.发送文件 ");printf("3.退出\n\n");
}void messageSend(MES *UWmes)
{pthread_mutex_lock(&Umutex);char targetUsername[SIZE];printf("请输入目标用户名:");if (!fgets(targetUsername, SIZE, stdin)){printf("输入错误\n");pthread_mutex_unlock(&Umutex);return;}targetUsername[strcspn(targetUsername, "\n")] = 0;if (strcmp(targetUsername, "quit") == 0){quitFlag = 1;pthread_mutex_unlock(&Umutex);return;}strncpy(UWmes->targetname, targetUsername, SIZE - 1);UWmes->messageType = 1;printf("请输入消息内容:");if (!fgets(UWmes->note, SIZE, stdin)){printf("输入错误\n");pthread_mutex_unlock(&Umutex);return;}UWmes->note[strcspn(UWmes->note, "\n")] = 0;int total = 0;int size = sizeof(MES);char *ptr = (char *)UWmes;while (total < size){int n = write(sockUserFd, ptr + total, size - total);if (n <= 0){perror("发送消息失败");break;}total += n;}pthread_mutex_unlock(&Umutex);
}void fileSend(MES *UWmes)
{pthread_mutex_lock(&Umutex);char targetUsername[SIZE];printf("请输入目标用户名:");if (!fgets(targetUsername, SIZE, stdin)){printf("输入错误\n");pthread_mutex_unlock(&Umutex);return;}targetUsername[strcspn(targetUsername, "\n")] = 0;strncpy(UWmes->targetname, targetUsername, SIZE - 1);UWmes->messageType = 2;printf("请输入文件路径:");char filePath[SIZE];if (!fgets(filePath, SIZE, stdin)){printf("输入错误\n");pthread_mutex_unlock(&Umutex);return;}filePath[strcspn(filePath, "\n")] = 0;int fd = open(filePath, O_RDONLY);if (fd < 0){perror("文件打开失败");pthread_mutex_unlock(&Umutex);return;}int fdSize = read(fd, UWmes->file.data, FDSIZE);if (fdSize < 0){perror("文件读取失败");close(fd);pthread_mutex_unlock(&Umutex);return;}UWmes->file.size = fdSize;strncpy(UWmes->file.name, filePath, SIZE - 1);int total = 0;int size = sizeof(MES);char *ptr = (char *)UWmes;while (total < size){int n = write(sockUserFd, ptr + total, size - total);if (n <= 0){perror("发送文件失败");break;}total += n;}close(fd);pthread_mutex_unlock(&Umutex);
}void cleanStdin()
{int ch;while ((ch = getchar()) != '\n' && ch != EOF);
}
一、整体结构
本项目是基于 TCP 的聊天系统,分为 服务端 和 客户端 两部分,支持:
-
用户私聊
-
文件传输
-
在线用户列表广播
-
动态线程管理与回收
核心思想:
-
服务端监听端口,接收用户连接。
-
每个用户连接由独立线程处理(
functionUser
)。 -
使用链表维护在线用户信息。
-
通过
MES
结构体进行统一消息封装。
二、主要数据结构
1. 消息结构体 MES
typedef struct message {int messageType; // 0服务器消息,1用户消息,2文件消息char targetname[32]; // 目标用户名char note[SIZE]; // 消息内容struct File file; // 文件结构体
} MES;
-
messageType:区分普通消息、私聊消息、文件消息。
-
note:文本消息或提示。
-
file:包含文件名、内容和大小。
2. 客户端链表节点 CN
typedef struct ClientNode {pthread_t threadid; // 用户线程IDint userfd; // 用户套接字struct sockaddr_in userAddr; // 用户地址char username[SIZE]; // 用户名struct ClientNode *next; // 链表指针int isFinished; // 线程退出标志
} CN;
作用:维护在线用户信息,支持查找、删除、广播。
三、服务端逻辑
1. 主流程(main
)
-
创建
sockServerFd
,绑定 IP/端口并监听。 -
启动两个后台线程:
-
functionServer
:接收新连接,分配线程处理用户。 -
functionMonitor
:负责线程回收和链表清理。
-
-
主线程等待子线程退出。
2. functionServer
-
使用
accept
接收新用户。 -
读取用户名,创建用户线程
functionUser
。 -
调用
Node
将用户加入链表。 -
向该用户发送当前在线用户列表。
-
向所有人广播“某用户上线”。
3. functionUser
-
循环
read
用户发来的MES
消息。 -
判断消息类型:
-
1:私聊消息 → 调用
transmitUserMessage
转发。 -
2:文件消息 → 转发给目标用户。
-
-
用户断开时:
-
标记链表节点
isFinished=1
。 -
广播“用户下线”。
-
4. functionMonitor
-
定时扫描链表。
-
如果某线程
isFinished=1
,pthread_join
并释放链表节点。
四、客户端逻辑
1. 主流程(main
)
-
创建
sockUserFd
,连接服务器。 -
输入用户名并发送给服务器。
-
创建两个线程:
-
functionRead
:接收服务器/用户消息并打印。 -
functionWrite
:交互式输入,发送消息或文件。
-
2. functionRead
-
循环读取
MES
,根据类型输出:-
0:服务器消息
-
1:私聊消息
-
2:文件消息(带文件名与大小)
-
3. functionWrite
-
菜单操作:
-
1:调用
messageSend
,发送文本消息。 -
2:调用
fileSend
,发送文件。 -
3:退出,设置
quitFlag
,关闭读端,让functionRead
退出。
-
五、关键函数说明
-
Node
:添加用户节点到链表。 -
deleteNode
:删除指定线程对应的节点。 -
transmitUserMessage
:根据目标用户名转发消息或文件。 -
sendOnlineUsers
:将在线用户列表发送给新上线用户。 -
broadcastMessage
:向所有用户广播消息。
六、注意事项
-
并发安全:链表和套接字操作均使用
pthread_mutex_t
加锁。 -
粘包问题:这里直接以
MES
结构体作为传输单元,避免了粘包问题。 -
文件传输限制:文件内容存储在
FDSIZE
数组,大小有限,适合小文件。 -
线程回收:通过
isFinished
标记 +functionMonitor
循环清理,避免僵尸线程。