TCP和UDP与IO模型
关键词: tcp udp
主要内容:
- tcp
- udp
- tcp 多路复用
- udp 非阻塞
文档参考:
最新地址: https://taotaodiy-linux.readthedocs.io/en/latest/linux/base/tcpudp.html
tcp 实现代码 (C语言)
一对一通信代码
客户端
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(void)
{
int cli_socket;
int ret;
struct sockaddr_in serv_addr;
char buf[128];
/*
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain:
AF_UNIX 进程间通信 socket
AF_INET ipv4 通信
AF_PACKET 原始套接字
type:
SOCK_STREAM 流式套接字 tcp通信
SOCK_DGRAM 数据包套接字 udp通信
SOCK_RAW 原始套接字
protocol
0
*/
cli_socket = socket(AF_INET,SOCK_STREAM,0);
if(cli_socket < 0){
perror("socket err");
return -34;
}
/*向服务器 发起连接请求
connect
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
struct sockaddr_in {
sa_family_t sin_family; AF_INET
in_port_t sin_port; port 16bit 网络字节序
struct in_addr sin_addr; internet address ip地址
};
Internet address.
struct in_addr {
uint32_t s_addr; address in network byte order
};
*/
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8888);
serv_addr.sin_addr.s_addr = inet_addr("132.232.72.168");
ret = connect(cli_socket, (struct sockaddr *)&serv_addr,sizeof(serv_addr));
if(ret < 0 ){
perror("connect err");
return -34;
}
while(1){
memset(buf,0,sizeof(buf));
//strcpy(buf,"hello ,this is cli msg!");
scanf("%s", buf);
ret = send(cli_socket,buf,sizeof(buf),0);
if(ret <0){
perror("cli send err");
return -31;
}
memset(buf,0,sizeof(buf));
ret = recv(cli_socket,buf,sizeof(buf),0);
if(ret <0){
perror("recv err");
return -322;
}
if(ret == 0){
printf("cli recv get 0\n");
return -345;
}
printf("cli get serv msg:%s\n",buf);
sleep(1);
}
close(cli_socket);
}
服务器端
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(void)
{
int listen_socket;
int cli_socket;
int ret;
struct sockaddr_in serv_addr;
char buf[128];
/*
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain:
AF_UNIX 进程间通信 socket
AF_INET ipv4 通信
AF_PACKET 原始套接字
type:
SOCK_STREAM 流式套接字 tcp通信
SOCK_DGRAM 数据包套接字 udp通信
SOCK_RAW 原始套接字
protocol
0
*/
listen_socket = socket(AF_INET,SOCK_STREAM,0);
if(listen_socket < 0){
perror("socket err");
return -34;
}
/*
给socket 指定 ip和端口号
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
struct sockaddr_in {
sa_family_t sin_family; AF_INET
in_port_t sin_port; port 16bit 网络字节序
struct in_addr sin_addr; internet address ip地址
};
Internet address.
struct in_addr {
uint32_t s_addr; address in network byte order
};
*/
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8888);
//serv_addr.sin_addr.s_addr = inet_addr("132.232.72.168");
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
ret = bind(listen_socket,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
if(ret <0){
perror("bind err");
return -34;
}
/*
int listen(int sockfd, int backlog);
backlog:
同一个瞬间,可以同时处理多少个请求
2*backlog+1
*/
ret = listen(listen_socket,5);
if(ret <0){
perror("listen err");
return -34;
}
/*当有 客户端连接上来的时候,accept 返回
返回一个 新的socket,该新的socket 用于和客户端通信,表示一个新的连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
addr: 对方/客户端的 ip地址和端口
*/
cli_socket = accept(listen_socket,NULL,NULL);
if(cli_socket <0){
perror("accept err");
return -23;
}
while(1){
/*
服务器, 先收后发
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd:通信socket
buf:接收的数据存放的位置
len: 你要求收取多少数据
flags: 0
返回值,
>0 实际收取的大小
=0
<0 表示收取失败
*/
memset(buf,0,sizeof(buf));
ret = recv(cli_socket,buf,sizeof(buf),0);
if(ret<0){
printf("recv err %d\n",errno);
return -25;
}
if(ret == 0){
printf("recv ==0 \n");
return -34;
}
printf("serv get data:%s\n",buf);
/*解析数据*/
memset(buf,0,sizeof(buf));
strcpy(buf,"Ur MSG got, this is server!");
ret = send(cli_socket,buf,sizeof(buf),0);
if(ret<0){
printf("send err %d\n",errno);
return -25;
}
}
close(cli_socket);
}
comm.h 放传输数据用的结构体(也就是所谓的协议)
#ifndef COMM_HHH_H
#define COMM_HHH_H
struct msg {
char type;
short sval;
int ival;
char des[128];
};
#endif
makefile
all:
gcc client.c -o client
gcc server.c -o server
一对多通信代码
一对多通信 主要是服务器代码不同,这里用多线程
服务器端
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include "comm.h"
struct pri {
pthread_t pid;
int socket;
int xpid;
//...................
};
void *serv_for_cli (void *args)
{
int ret;
struct pri *pri = args;
int cli_socket = pri->socket;
struct msg msg;
while(1){
memset(&msg,0,sizeof(msg));
ret = recv(cli_socket,&msg,sizeof(msg),0);
if(ret<0){
printf("recv err %d\n",errno);
goto end;
}
if(ret == 0){
printf("recv ==0 peer client shutdown!!!!\n");
goto end;
}
printf("serv get type%d sval%d ival%d des:%s\n",
msg.type,ntohs(msg.sval), ntohl(msg.ival),msg.des);
/*解析数据*/
msg.type++;
msg.sval = ntohs(msg.sval);
msg.sval++;
msg.sval = htons(msg.sval);
strcpy(msg.des,"Ur MSG got, this is server!");
ret = send(cli_socket,&msg,sizeof(msg),0);
if(ret<0){
printf("send err %d\n",errno);
goto end;
}
}
end:
free(pri);
close(cli_socket);
return NULL;
}
int main(void)
{
int listen_socket;
int cli_socket;
int ret;
struct pri *pri;
struct sockaddr_in serv_addr;
struct sockaddr_in cli_addr;
socklen_t len;
listen_socket = socket(AF_INET,SOCK_STREAM,0);
if(listen_socket < 0){
perror("socket err");
return -34;
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(3100);
//serv_addr.sin_addr.s_addr = inet_addr("192.168.2.92"); //指定服务器ip
serv_addr.sin_addr.s_addr = INADDR_ANY; //给一个0,bind发现 ip为0,则自动获取主机ip地址
ret = bind(listen_socket,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
if(ret <0){
perror("bind err");
return -34;
}
ret = listen(listen_socket,5);
if(ret <0){
perror("listen err");
return -34;
}
loop:
len = sizeof(cli_addr);
memset(&cli_addr,0,sizeof(cli_addr));
cli_socket = accept(listen_socket,(struct sockaddr *)&cli_addr,&len);
if(cli_socket <0){
perror("accept err");
return -23;
}
printf("serv get client conncet: cli ip %s port %d\n",
inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port) );
pri = malloc(sizeof(*pri));
pri->socket = cli_socket;
pthread_create(&pri->pid, NULL,serv_for_cli,pri);
goto loop;
close(listen_socket);
}
udp 实现代码 (C语言)
{.align-center}
客户端
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "comm.h"
int main(void)
{
int ret;
int cli_socket;
socklen_t addrlen;
struct sockaddr_in serv_addr;
struct msg msg;
cli_socket = socket(AF_INET,SOCK_DGRAM, 0);
if(cli_socket < 0 ){
perror("udp serv socket err");
return -34;
}
memset(&msg,0,sizeof(msg));
msg.type = 0;
msg.sval = 12;
msg.ival = 124;
strcpy(msg.des,"hello comes from cli");
while(1){
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(3100);
serv_addr.sin_addr.s_addr = inet_addr("192.168.2.92");
ret = sendto(cli_socket,&msg,sizeof(msg),0,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
if(ret < 0){
printf("sendto err%d\n",ret);
return -34;
}
memset(&msg,0,sizeof(msg));
addrlen = sizeof(serv_addr);
ret = recvfrom(cli_socket, &msg, sizeof(msg), 0,(struct sockaddr *)&serv_addr, &addrlen);
if(ret <=0){
printf("recvfrom err%d\n",ret);
return -34;
}
printf("cli get msg: type%d sval%d ival%d des:%s\n",
msg.type,msg.sval,msg.ival,msg.des);
sleep(1);
}
}
服务器端
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "comm.h"
int main(void)
{
int ret;
int serv_socket;
struct sockaddr_in serv_addr;
struct sockaddr cli_addr;
socklen_t addrlen;
struct msg msg;
/*
int socket(int domain, int type, int protocol);
domain:
AF_UNIX 进程间通信 socket
AF_INET ipv4 通信
AF_PACKET 原始套接字
type:
SOCK_STREAM 流式套接字 tcp通信
SOCK_DGRAM 数据包套接字 udp通信
SOCK_RAW 原始套接字
protocol
0
*/
serv_socket = socket(AF_INET,SOCK_DGRAM, 0);
if(serv_socket < 0 ){
perror("udp serv socket err");
return -34;
}
/*
给socket 指定 ip和端口号
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
struct sockaddr_in {
sa_family_t sin_family; AF_INET
in_port_t sin_port; port 16bit 网络字节序
struct in_addr sin_addr; internet address ip地址
};
Internet address.
struct in_addr {
uint32_t s_addr; address in network byte order
};
*/
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(3100);
serv_addr.sin_addr.s_addr = INADDR_ANY; //给一个0,bind发现 ip为0,则自动获取主机ip地址
ret = bind(serv_socket,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
if(ret <0){
perror("bind err");
return -34;
}
while(1){
/*
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
buf,len 用于存放接收到的数据
src_addr,存放对方/客户端 的地址
addrlen, 双向参数 你要告诉recvfrom 你的地址长度有多大, 该函数返回,告诉你真实的长度是多少
返回值
>=0 正确接收到的数据
<0 出错
*/
memset(&msg,0,sizeof(msg));
addrlen = sizeof(cli_addr);
ret = recvfrom(serv_socket, &msg, sizeof(msg), 0,&cli_addr, &addrlen);
if(ret <=0){
printf("recvfrom err%d\n",ret);
return -34;
}
printf("serv get msg: type%d sval%d ival%d des:%s\n",
msg.type,msg.sval,msg.ival,msg.des);
msg.type++;
msg.sval++;
/*
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
*/
ret = sendto(serv_socket,&msg,sizeof(msg),0,&cli_addr,sizeof(cli_addr));
if(ret < 0){
printf("sendto err%d\n",ret);
return -34;
}
}
}
tcp的多路复用(C语言)
普通tcp和udp使用就是同步阻塞,recv()、recvfrom()等函数就是阻塞死等;
这里主要是
套接字的非阻塞和tcp的多路复用,udp天生具有并发,就不画蛇添足了。
服务器端
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <unistd.h>
#include "comm.h"
/*
void FD_CLR(int fd, fd_set *set); 将fd从set清除
int FD_ISSET(int fd, fd_set *set); 判断fd是否存在集合中
void FD_SET(int fd, fd_set *set); 将fd加入到set
void FD_ZERO(fd_set *set); 清空set
阻塞式的监控
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
nfds:
集合中所有fd的 最大值+1
readfds: 可读事件,表示 当前可读fd的 一个集合
如果你想监控 某些socket/fd 的可读事件,那么 就将他们加入到该集合
双向参数: 你在调用之前,将需要监控的所有fd都加进去
等select返回的时候,里面只保存了 发生事件的fd
所以, 你需要备份
writefds: 可写...................
如果你想监控 .....................可写.................
双向参数: 你在调用之前,将需要监控的所有fd都加进去
等select返回的时候,里面只保存了 发生事件的fd
所以, 你需要备份
exceptfds:异常事件,表示 fd发生异常的集合
如果你想监控 异常事件 ............................
双向参数: 你在调用之前,将需要监控的所有fd都加进去
等select返回的时候,里面只保存了 发生事件的fd
所以, 你需要备份
timeout: 指定等待的最大时间,如果在改时间内 没有 任何fd发生事件,
返回
struct timeval {
long tv_sec;
long tv_usec;
};
==NULL, 永久监控
==非阻塞监控timeout->tv_sec=timeout->tv_usec = 0;
返回值:
-1: 错误
正值: 发生事件的fd的个数
0: 超时了
*/
struct sockaddr_in serv_local;
struct sockaddr_in cli_addr;
size_t addr_sz;
int socket_serv;
int socket_client;
fd_set readfds; //读集合
fd_set readfds_backup; //读集合
int fd_max;
struct timeval timeout;
int fd_client[128];
int fd_client_cnt = 0;
int serv_for_client(int socket_client)
{
int ret;
struct msg_type msg;
memset(&msg,0,sizeof(msg));
ret = recv(socket_client,&msg,sizeof(msg),0);
if(ret <0){
perror("recv err");
close(socket_client);
return -35;
}else if(ret == 0){
printf("server detect client over\n");
close(socket_client);
return -25;
}
printf("serv get msg:type%d tmp%d himmdy%d des: %s\n",
msg.msg_type,ntohs(msg.tmp),ntohl(msg.himmdty),msg.des);
msg.tmp = ntohs(msg.tmp);
msg.himmdty = ntohl(msg.himmdty);
msg.tmp++; //主机字节序
msg.himmdty += 3;
strcpy(msg.des,"hello from serv");
msg.tmp = htons(msg.tmp);
msg.himmdty = htonl(msg.himmdty);
ret = send(socket_client, &msg, sizeof(msg),0);
if(ret <0){
perror("send err");
close(socket_client);
return -36;
}
return 0;
}
int main(int argc,char **argv)
{
int ret;
int idx;
socket_serv = socket(AF_INET,SOCK_STREAM, 0);
if(socket_serv <0 ){
perror("server socket err");
return -12;
}
serv_local.sin_family = AF_INET;
serv_local.sin_port = htons(5050);
serv_local.sin_addr.s_addr = INADDR_ANY; /*INADDR_ANY = 0 代码自动绑定本机ip地址*/
ret = bind(socket_serv,(struct sockaddr *)&serv_local,sizeof(serv_local));
if(ret <0){
perror("bind err");
return -13;
}
ret = listen(socket_serv,5);
if(ret <0){
perror("listen err");
return -14;
}
/*当前有一个socket,那么 加入到监控集合中去
*/
FD_ZERO(&readfds);
FD_SET(socket_serv,&readfds);
fd_max = socket_serv;
loop:
timeout.tv_sec = 5;
timeout.tv_usec = 0;
readfds_backup = readfds;
ret = select(fd_max+1,&readfds_backup,NULL,NULL,&timeout);
if(ret <0){
perror("select err");
return -23;
}else if(ret == 0){
printf("select timeout, next time\n");
goto loop;
}
/*readfds_backup 里面保存了发生事件的fd,遍历*/
if(FD_ISSET(socket_serv,&readfds_backup)){ //有新的客户端发起连接
memset(&cli_addr,0,sizeof(cli_addr));
addr_sz = sizeof(cli_addr);
socket_client = accept(socket_serv,(struct sockaddr *)&cli_addr,&addr_sz);
if(socket_client < 0){
perror("serv accept err");
return -34;
}
printf("serv get client ip %s port %d\n",
inet_ntoa(cli_addr.sin_addr) ,ntohs(cli_addr.sin_port));
/*将新的套接字 加入监控空集合*/
FD_SET(socket_client,&readfds);
fd_max = fd_max > socket_client ? fd_max : socket_client;
fd_client[fd_client_cnt++] = socket_client;
}
for(idx=0;idx<fd_client_cnt;idx++){
if(FD_ISSET(fd_client[idx],&readfds_backup)){
ret = serv_for_client(fd_client[idx]);
if(ret <0){
fd_client[idx] = 0; //清空
}
}
}
goto loop; //继续监控
close(socket_serv);
return 0;
}
非阻塞UDP(C语言)
服务器端
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include "comm.h"
int main(void)
{
int flags;
int ret;
int serv_socket;
struct sockaddr_in serv_addr;
struct sockaddr cli_addr;
socklen_t addrlen;
struct msg msg;
serv_socket = socket(AF_INET,SOCK_DGRAM, 0);
if(serv_socket < 0 ){
perror("udp serv socket err");
return -34;
}
flags = fcntl(serv_socket,F_GETFL,0);
flags |= O_NONBLOCK;
fcntl(serv_socket,F_SETFL,flags);
/*如何让 socket/fd 编程非阻塞呢
1.读取 socket里面的 flags
flags = fcntl(socket,F_GETFL,0);
2.将flags对应的bit置一
flags |= O_NONBLOCK;
3.将flags写入socket文件内部
fcntl(socket,F_SETFL,flags);
*/
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(3100);
serv_addr.sin_addr.s_addr = INADDR_ANY; //给一个0,bind发现 ip为0,则自动获取主机ip地址
ret = bind(serv_socket,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
if(ret <0){
perror("bind err");
return -34;
}
while(1){
/*
非阻塞 返回值的问题
1.正确收到数据 ret>0
2.没有数据 ret<0 errno==EAGAIN
3.出错 ret<0
*/
loop:
memset(&msg,0,sizeof(msg));
addrlen = sizeof(cli_addr);
ret = recvfrom(serv_socket, &msg, sizeof(msg), 0,&cli_addr, &addrlen);
if(ret < 0){
if(errno == EAGAIN){
printf("recv current nodata,try next\n");
void do_something_other(void);
sleep(1);
goto loop;
}
printf("recvfrom err%d\n",ret);
return -34;
}
printf("serv get msg: type%d sval%d ival%d des:%s\n",
msg.type,msg.sval,msg.ival,msg.des);
msg.type++;
msg.sval++;
ret = sendto(serv_socket,&msg,sizeof(msg),0,&cli_addr,sizeof(cli_addr));
if(ret < 0){
printf("sendto err%d\n",ret);
return -34;
}
}
}