摘要:大家都知道不同pc间的通信需要用到套接字sockte来实现,但是服务器一次只能收到一个客户端发来的消息,所以为了能让服务器可以接收多个客户端的连接与消息的传递,我们就引入了多进程并发这样一个概念。听名字就可以知道--需要用到进程,当然也有多线程并发今天我们讲进程的就可以了,线程的同理。
基本原理: 每连接一个客户端,创建一个子进程,子进程负责处理connfd(客户请求)
父进程处理sockfd(连接请求)。
常用函数:
socket() 创建套接字
bind() 绑定本机地址和端口
connect() 建立连接
listen() 设置监听套接字
accept() 接受TCP连接
recv(), read(), recvfrom() 数据接收
send(), write(), sendto() 数据发送
close(), shutdown() 关闭套接字
各函数使用方法和参数返回值:
socket() 创建套接字
int socket (int domain, int type, int protocol);
domain 是地址族
PF_INET // internet 协议
PF_UNIX // unix internal协议
PF_NS // Xerox NS协议
PF_IMPLINK // Interface Message协议
type // 套接字类型
SOCK_STREAM // 流式套接字
SOCK_DGRAM // 数据报套接字
SOCK_RAW // 原始套接字
protocol 参数通常置为0
connect() 建立连接
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
返回值:0 或 -1
sockfd : socket返回的文件描述符
serv_addr : 服务器端的地址信息
addrlen : serv_addr的长度
bind() 绑定本机地址和端口
int bind (int sockfd, struct sockaddr* addr, int addrLen);
sockfd 由socket() 调用返回
addr 是指向 sockaddr_in 结构的指针,包含本机IP 地址和端口号
struct sockaddr_in
u_short sin_family // protocol family
u_short sin_port // port number
struct in_addr sin_addr //IP address (32-bits)
addrLen : sizeof (struct sockaddr_in)
listen() 设置监听套接字
sockfint listen (int sockfd, int backlog);
sockfd:监听连接的套接字
backlog(一般填5或10)
指定了正在等待连接的最大队列长度,它的作用在于处理可能同时出现的几个连接请求。
DoS(拒绝服务)攻击即利用了这个原理,非法的连接占用了全部的连接数,造成正常的
连接请求被拒绝。
返回值: 0 或 -1
完成 listen() 调用后, socket 变成了监听socket(listening socket)
accept() 接受TCP连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) ;
返回值:已建立好连接的套接字或-1
sockfd : 监听套接字
addr : 对方地址
addrlen:地址长度
rend( ) 发送数据
ssize_t send(int socket, const void *buffer, size_t length,int flags);
返回值:
成功:实际发送的字节数
失败:-1, 并设置errno
buffer : 发送缓冲区首地址
length : 发送的字节数
flags : 发送方式(通常为0)
recv( ) 接收数据
ssize_t recv(int socket, const void *buffer, size_t length,int flags);
返回值:
成功:实际接收的字节数
失败:-1, 并设置errno
buffer : 发送缓冲区首地址
length : 发送的字节数
flags : 接收方式(通常为0)
read()和write()经常会代替recv()和send(),通常情况下,看程序员的偏好使用read()/write()和recv()/send()时最好统一。
TCP服务器和客户端搭建流程是:
服务器:
socket() --> bind() --> listen() --> accept() --> read/write or recv/send
-->close()
客户端:
socket() --> connect() --> read/write or recv/send -->close()
知道流程我们就可以来创建服务器与客户端了
先来看服务器的搭建:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define N 1024
int sock_func(int port);
int recv_data(int connfd);
void signal_handler(int sig);
int main(int argc, char *argv[])
{
signal(SIGCHLD,signal_handler); //子进程结束时 回收资源
int sockfd = sock_func(8090); //调用服务器创建函数,传递端口参数 局域网可自行设置(0-65535,但建议>1024)
//公网的话要在(5000-65535)以内,前面的被大公司些用了。不可用
struct sockaddr_in cliaddr;
int len = sizeof(cliaddr);
while(1) //循环等待客户端连接
{
int connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len);//等待并接收客户端的连接请求
if(connfd == -1) //出错处理
{
perror("accept");
return -1;
}
printf("accept is successful\n");
printf("client ip: %s port is:%d\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port)); //打印客户端IP和端口
pid_t pid = fork(); //创建子进程
if (pid<0)
{
perror("fork");
return -1;
}
else if(pid == 0) //子进程
{
close(sockfd); //关闭继承过来的本机套接字描述符
recv_data(connfd); //传递客户端套接字去到接收数据函数模块
return 0;
}
else
{
continue; //跳出本次循环开始等待下一个客户端连接
}
}
close(sockfd);
return 0;
}
int sock_func(int port) //创建服务器模块
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);//创建套接字
if(sockfd == -1)
{
perror("scoket");
return -1;
}
printf("socket is succssful\n");
int on = 1;
int k = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));//端口复用函数:解决端口号被系统占用的情况
if(k == -1)
{
perror("setsockopt");
return -1;
}
struct sockaddr_in saddr;
bzero(&saddr,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = inet_addr("0"); //"0"代表匹配本机ip
saddr.sin_port =htons(port); //绑定端口
char buf[N]={0};
int ret1 = bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));//绑定服务器IP地址和端口
if(ret1 == -1)
{
perror("bind");
return -1;
}
printf("bind is successful\n");
int ret2 = listen(sockfd,5);//设置监听
if(ret2 == -1)
{
perror("listen");
return -1;
}
printf("linstening......\n");
return sockfd;
}
int recv_data(int connfd) //接收数据模块
{
char buf[N]={0}; //接收数据缓冲区
while(1) //循环接收客户端发送的数据
{
int n = recv(connfd,buf,N,0);//接收客户端发送的数据
if(n<0) //出错
{
perror("recv");
return -1;
}
else if(n == 0) //客户端关闭
{
close(connfd);//关闭客户端套接字
break;
}
printf(">>:%s\n",buf); //打印客户端发送的消息
memset(buf,0,sizeof(buf)); // 清空本次数据
}
}
void signal_handler(int sig)//回收子进程资源
{
waitpid(-1,NULL,WNOHANG);//非阻塞等待子进程结束,回收子进程资源
}
再来看看客户端的代码实现(这个可以连接任意一个TCP的服务器):
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define N 1024
int main(int argc, char *argv[])
{
if(argc<3) //输入参数不匹配情况
{
printf("%s \n",argv[0]);
return -1;
}
char buf[N]={0}; //定义缓冲区存放要发送的数据
struct sockaddr_in saddr;
socklen_t peerlen;
saddr.sin_family = AF_INET; //使用ipv4
saddr.sin_addr.s_addr = inet_addr(argv[1]); //输入服务器ip
saddr.sin_port =htons(atoi(argv[2])); //输入服务器端口
int sockfd = socket(AF_INET,SOCK_STREAM,0); //创建套接字
if(sockfd == -1) //出错处理
{
perror("socket");
return -1;
}
int ret1 = connect(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));//连接服务器
if(ret1 == -1)
{
perror("connet");
return -1;
}
printf("connet is seccussful\n");
int ret2;
while(1) //循环发送信息
{ memset(buf,0,sizeof(buf)); //每次清空缓冲区
fgets(buf,N,stdin); //写入缓冲区
buf[strlen(buf)-1]='\0';
ret2 = send(sockfd,buf,strlen(buf),0);//发送至服务器
if(ret2 == -1)
{
perror("send");
return -1;
}
if(strcmp(buf,"quit\n")==0) //设置退出条件
{
printf("quiting....\n");
break;
}
}
close(sockfd);
return 0;
}
来看看运行结果:(左边为客户端,右边为服务器)
好了,快拿代码去和拿的小伙伴们玩吧!
今天的代码就分享到这里了,哪里写的不对的,希望各位姥爷多多指正!
下期分享广播和组播的搭建,欢迎来访。