LINUX --- 网络编程(二)
1. 字节流特点导致的粘包问题
发送端:操作系统会根据 TCP 缓冲区情况进行数据包的合并优化(Nagle 算法),导致多个小消息可能被合并发送;
接收端:TCP 缓冲区可能将多个消息的字节流合并成一个数据块传递给应用层,造成粘包
粘包处理方案(确立消息边界)
a. 结束标志法
使用特殊字符(如\r\n)作为消息结束的标志,接收方遇到该标志即认为一个完整消息结束 。 b. 固定长度法
规定每个消息的固定长度(如 100 字节),不足则补位,接收方每次读取固定长度的数据。 c. 自定义消息结构体法
设计包含消息头和消息体的结构体,消息头中包含消息类型和消息长度等信息。
// 自定义消息结构体
typedef struct {int type; // 消息类型 (-1: 结束消息, 0: 普通消息, 其他: 自定义类型)int length; // 消息体长度char body[256]; // 消息体内容
} msg_t;
2.UDP 协议
特点总结
无连接:通信前不需要建立连接,直接发送数据
不可靠:不保证数据送达,也不保证顺序
数据报:以数据报为单位传输,有消息边界,不会产生粘包问题
高效性:开销小,速度快,适合实时应用
编程:
c/s
udp客户端:
socket
sendto
udp服务器:
socket
bind //固定的地址信息
recvfrom
//connect //给对应的服务器发了客户端的地址信息
//send
//sendto(,cliaddr,);
UDP 聊天室小练习,具体思路以及代码实现:
定义消息结构体(包含类型、用户名、文本等)
客户端实现(发送线程、接收线程)
服务器实现(维护客户端列表、处理不同类型消息、广播消息)
详细的步骤说明:
首先定义消息结构体和常量:
消息类型枚举(登录、聊天、下线)
消息结构体(类型、用户名、文本内容)
客户端信息结构体(地址信息、用户名、在线状态)
然后实现客户端:
主函数:获取用户名,创建发送和接收线程
发送线程:读取用户输入,根据输入内容发送不同类型的消息
接收线程:接收服务器转发的消息并显示
服务器实现:
主函数:初始化,创建客户端列表,循环接收消息并处理
处理登录消息:添加客户端到列表,广播登录信息
处理聊天消息:广播聊天内容给所有在线客户端
处理下线消息:更新客户端状态,广播下线信息
广播函数:将消息发送给所有在线客户端
#include "head.h"struct cliAddr
{struct sockaddr_in addr;int state;
}addrInfo[10];int broadcast_msg(int fd, struct sockaddr_in *cliaddr,msg_t *msg)
{int i = 0;for (i = 0; i < 10; ++i){printf("----%d---\n",__LINE__);if (addrInfo[i].state == LOGIN && memcmp(&addrInfo[i].addr,cliaddr,sizeof(struct sockaddr_in)) != 0 ){sendto(fd,msg,sizeof(msg_t),0,(const struct sockaddr*)&addrInfo[i].addr,sizeof(struct sockaddr));}}
}//客户端
int main(int argc, const char *argv[])
{//step0: socketint serfd = socket(AF_INET,SOCK_DGRAM,0);if (serfd < 0){perror("socket fail");return -1;}struct sockaddr_in seraddr;bzero(&seraddr,sizeof(seraddr));seraddr.sin_family = AF_INET;seraddr.sin_port = htons(50000);seraddr.sin_addr.s_addr = inet_addr("127.0.0.1");if (bind(serfd,(const struct sockaddr*)&seraddr,sizeof(seraddr)) < 0){perror("bind fail");return -1;}//step1: 接收消息 int i = 0;msg_t msg;struct sockaddr_in cliaddr;socklen_t len = sizeof(cliaddr);while (1){recvfrom(serfd,&msg,sizeof(msg),0,(struct sockaddr*)&cliaddr,&len);printf("type = %d\n",msg.type);printf("name = %s\n",msg.name);printf("text = %s\n",msg.text);printf("LOGIN = %d\n",LOGIN);switch (msg.type){case LOGIN://addrInfo[0].addr = cliaddr;//addrInfo[0].state = LOGIN;//1.找位置 //printf("--LOGIN---\n");for (i = 0; i < 10; ++i){if(addrInfo[i].state == 0 || addrInfo[i].state == QUIT)break;}addrInfo[i].addr = cliaddr;addrInfo[i].state = LOGIN;printf("----%d---\n",__LINE__);broadcast_msg(serfd,&cliaddr,&msg);break;case CHAT:printf("--CHAT--\n");broadcast_msg(serfd,&cliaddr,&msg);break;case QUIT:printf("--QUIT-\n");broadcast_msg(serfd,&cliaddr,&msg);for (i = 0; i < 10; ++i){if (memcmp(&addrInfo[i].addr,&cliaddr,sizeof(struct sockaddr_in)) == 0 ){addrInfo[i].state = QUIT;}}break;}}return 0;
}
#include "head.h"msg_t msg;
struct sockaddr_in seraddr;
int clifd;void * do_recv(void *arg)
{msg_t msg1;while (1){recvfrom(clifd,&msg1,sizeof(msg1),0,NULL,NULL);printf("[%s]:%s\n",msg1.name,msg1.text);}}void * do_send(void *arg)
{while (1){fgets(msg.text,sizeof(msg.text),stdin);if (strncmp(msg.text,"quit",4) == 0){msg.type = QUIT;sendto(clifd,&msg,sizeof(msg),0,(const struct sockaddr*)&seraddr,sizeof(seraddr));close(clifd);exit(0);}else {msg.type = CHAT;}sendto(clifd,&msg,sizeof(msg),0,(const struct sockaddr*)&seraddr,sizeof(seraddr));}
}//客户端
int main(int argc, const char *argv[])
{//step0: socketclifd = socket(AF_INET,SOCK_DGRAM,0);if (clifd < 0){perror("socket fail");return -1;}//step1: 组织 登录的消息 msg.type = LOGIN;printf("Input your name:");fgets(msg.name,sizeof(msg.name),stdin);msg.name[strlen(msg.name) - 1] = '\0';sprintf(msg.text,"%s is login!\n",msg.name);printf("LOGIN = %d\n",LOGIN);bzero(&seraddr,sizeof(seraddr));seraddr.sin_family = AF_INET;seraddr.sin_port = htons(50000);seraddr.sin_addr.s_addr = inet_addr("127.0.0.1");sendto(clifd,&msg,sizeof(msg),0,(const struct sockaddr*)&seraddr,sizeof(seraddr));//step3: pthread_t tid[2];int ret = pthread_create(&tid[0],NULL,do_recv,NULL);if (ret != 0)handle_error_en(ret,"pthread_create fail");ret = pthread_create(&tid[1],NULL,do_send,NULL);if (ret != 0)handle_error_en(ret,"pthread_create fail");pthread_join(tid[0],NULL);pthread_join(tid[1],NULL);return 0;
}
#include <stdio.h>
#include <string.h>
struct demo
{int a;short b;
};int main(int argc, const char *argv[])
{struct demo d1 = {100,99};struct demo d2 = {100,99};if (memcmp(&d1,&d2,sizeof(d1)) == 0){printf("d1 = d2\n");}else {printf("d1 != d2\n");}return 0;
}
#ifndef _HEAD_H_
#define _HEAD_H
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <pthread.h>enum MSG_TYPE
{LOGIN = 1,CHAT,QUIT
};typedef struct msg
{int type;char name[20];char text[256];
}msg_t;#define handle_error_en(en, msg) \do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)#define handle_error(msg) \do { perror(msg); exit(EXIT_FAILURE); } while (0)#endif