Socket编程(高并发TCP/UDP)
Socket编程(TCP/UDP) <sys/socket.h>
如有问题请多多指教,
首先我们需要知道tcp和udp的协议的特点。
1.Tcp模式(由客户端链接服务器)
服务器端 客户端
socket() 创建套接字 socket()创建套接字
bind() 命名套接字(绑定地址)
listen()创建监听队列
accept()接收连接(阻塞) connect()发起连接
recv() 接收数据 send()发送数据
Send()发送数据 recv()发送数据
Close()关闭连接 close()关闭链接
对于每一个主机我们都有大端和小端的区别,为啥保证可以正常通讯,我们网络字节序均使用了大端的方式。现在pc端大多数使用小端模式,因此我们的主机字节序使用小端。
<netinet/in.h>
unsigned long int htonl(unsigned long int hostlong)
unsigned short int htons(unsigned short int hostlong)
unsigned long int ntonl(unsigned long int netlong)
unsigned short int ntons (unsigned short int netshort)
例如:
htonl 表示 host to network long 即将长整型主机字节序的数据转换成网络字节序。
ip地址转换函数
通常,我们习惯用可读性好的字符串来表示ip地址,比如用点分十进制字符串表示IPv4地址,以及用十六进制字符串表示IPv6地址。但编程中我们需要将它们转化成整数方能使用。
用点分十进制字符串表示的IPv4地址和网络字节序整数表示的IPv4地址之间的转换:
#include<arpa/inet.h>
int_addr_t inet_addr(const char * strptr);
int inet_aton(const char * cp,struct in_addr* inp);
char * inet_ntoa(struct in_addr in);
inet_addr 将用点分十进制字符串表示的IPv4地址转换成网络字节序整数表示的IPv4,失败时返回 INADDR_NONE;
inet aton 函数和上面的一样,但是将转化的字符串存放在 inp中,成功返回1,失败返回0.
inet_ntoa 函数将用网络字节序整数表示ipv4 转化成 点分十进制字符串表示的IPv4地址但是要注意的是,该函数中,有静态变量,因此inet_nota是不可重入的。
函数介绍:
int socket(int domain, int type, int protocol);
失败返回 -1,成功返回监听套接字
domain 设置套接字协议族, AF_UNIX UNIX本地域协议簇, AF_INET TCP/IPV4协议簇 AF_INET6 TCP/IPV6协议簇
type 设置套接字服务类型 SOCK_STREAM (流服务) SOCK_UGRAM(数据报)
protocol 一般设置为0,使用默认协议
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
失败返回-1,成功返回0.
sockfd是监听套接字描述符,addr是ip地址结构,addrlen是地址长度。
struct sockaddr_in {
short sin_family; //地址族 Socket只能用AF_INET
u_short sin_port; //端口
struct in_addr sin_addr; //IPv4网络地址
char sin_zero[8]; //
};
int listen(int sockfd, int backlog);
成功返回0,失败返回-1,
sockfd 是监听套接字,backlog 监听队列的最大长度。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd 是监听套接字,addr 是连接的ip地址,addrlen 是长度。
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockfd是监听套接字,addr是被连接的ip地址,addrlen 是长度
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd是连接套接字,buf是收到的数组,len是长度,flags 是标志位
当返回值为0的时候就代表断开链接。
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd是连接套接字,buf是写入的数组,len是长度,flags是标志位
int close(int fd);
fd是套接字
在tcp中,在没有客户端连接的时候,我们回阻塞在accept()函数,等待有人来连接我们。
代码如下:
ser.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
//创建套接子
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd !=-1);
//指定ip + port
struct sockaddr_in caddr,saddr;// saddr, ser; caddr,cli
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000); //网络字节序列 大端
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res!=-1);
listen(sockfd,5);
while(1)
{
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
if( c < 0)
{
continue;
}
printf("accept c = %d\n",c);
char buff[128] = {0};
while(1)
{
int n = recv(c,buff,127,0);
if(n==0)
{
printf("one cli is over\n");
break;
}
printf("buff=%s\n",buff);
send(c,"ok",2,0);
}
close(c);
}
exit(0);
}
cli.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd !=-1);
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res!=-1);
while(1)
{
char buff[128] = {0};
printf("input:\n");
fgets(buff,128,stdin);
if(strncmp(buff,"end",3)==0)
{
break;
}
send(sockfd,buff,strlen(buff),0);
memset(buff,0,128);
recv(sockfd,buff,127,0);
printf("buff=%s\n",buff);
}
close(sockfd);
exit(0);
}
2.UDP模式:
服务器 客户端
socket()创建套接字 socket()创建套接字
bind()命名套接字
recvfrom() 收到数据 sendto() 发送数据
sendto() 发送数据 recvfrom() 收到数据
close() 关闭 close() 关闭
socket() bind() close() 函数和 tcp的操作一样,不一样的是 收发数据
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 是标志位,
dest_addr是对方的ip地址,addrlen是ip地址的长度。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
sockfd是监听套接字,buf是数组,len 是长度,flags是标志位,src_addr是对方的ip地址,addrlen是ip长度。
代码:
ser.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main()
{
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
assert(sockfd!=-1);
struct sockaddr_in saddr,caddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr* )&saddr,sizeof(saddr));
while(1)
{
int len = sizeof(caddr);
char buff[128] = {0};
int n = recvfrom(sockfd,buff,127,0,(struct sockaddr*)&caddr,&len);
printf("recv:%s\n",buff);
sendto(sockfd,"ok",2,0,(struct sockaddr*)&caddr,sizeof(caddr));
}
exit(0);
}
cli.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main()
{
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
assert(sockfd!=-1);
struct sockaddr_in saddr,caddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr* )&saddr,sizeof(saddr));
while(1)
{
char buff[128]={0};
printf("input:\n");
fgets(buff,128,stdin);
if(strncmp(buff,"end",3)==0)
{
break;
}
sendto(sockfd,buff,strlen(buff),0,(struct sockaddr*)&saddr,sizeof(caddr));
memset(buff,0,128);
int len = sizeof(saddr);
recvfrom(sockfd,buff,127,0,(struct sockaddr*)&saddr,&len);
printf("recv:%s\n",buff);
}
close(sockfd);
exit(0);
}
思考:
如果我们每次只接收一个字节的数据,如果我们发送hello tcp 和 udp 的结果是怎样的呢?
在tcp 中
在udp中
3.多线程tcp
在tcp 中,当有一个客户端连接服务器的时候,我们会进入到循环的收发过程中,这时候当我们在去链接其他的客户端的时候,就会发生阻塞,一直等待当我们正在收发的客户端断开连接后,才会连接入我们新的客户端。这时候我们可以将连接的过程写道主线程中,每次连接一个那我们就去将这个连接后的操作,交给一个线程去操作,以此保证不阻塞的操作。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
int create_socket();
//多线程处理高并发问题
void *fun(void *arg)
{
int c= (int )arg;
while(1)
{
char buff[128] = {0};
int n= recv(c,buff,127,0);
if(n<=0)
{
break;
}
printf("recv(%d)=%s\n",c,buff);
send(c,"ok",2,0);
}
printf("one cli is over\n");
close(c);
}
int main()
{
int sockfd = create_socket();
assert(sockfd != -1);
while(1)
{
struct sockaddr_in caddr;
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c<0)
{
continue;
}
pthread_t id;
pthread_create(&id,NULL,fun,(void *)c);
}
}
int create_socket()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
return -1;
}
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res==-1)
{
return -1;
}
listen(sockfd,5);
return sockfd;
}
4.多进程tcp
多进程的思想和多线程一样,但是在进程复制的时候,也会复制描述符,我们将没有用的描述符进行关闭,在当我们某一个客户端断开连接的时候,这个进程就要结束,这时候就会变成僵死进程,我们需要用信号进行处理。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>
#include <sys/wait.h>
void sig_fun(int sig)
{
wait(NULL);
}
int create_socket();
//多进程处理高并发问题
int main()
{
int sockfd = create_socket();
assert(sockfd != -1);
signal(SIGCHLD,sig_fun);
while(1)
{
struct sockaddr_in caddr;
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c<0)
{
continue;
}
pid_t pid = fork();
assert(pid!=-1);
if(pid == 0)
{
close(sockfd);
while(1)
{
char buff[128] = {0};
int n= recv(c,buff,127,0);
if(n<=0)
{
break;
}
printf("recv=%s\n",buff);
send(c,"ok",2,0);
}
close(c);
printf("onr cli is over\n");
exit(0);
}
close(c);
}
}
int create_socket()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
return -1;
}
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res==-1)
{
return -1;
}
listen(sockfd,5);
return sockfd;
}
上一篇: Kafka集群安装
下一篇: Kafka 客户端的缓存管理