在之前的文章已经详细介绍过socket网络编程 , 那么接下来让我们看看多线程的网络编程如何实现
大体的思路就是在accpet之后 , 创建一个新的线程供客户端所使用.
以下是服务器端代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
void *thread_fun(void *arg)
{
int c = (int)arg;
while(1)
{
char buff[128] = {0};
int n = recv(c,buff,1,0);
if(n <= 0)
{
break;
}
printf("buff[%d]=%s\n",c,buff);
send(c,"ok",2,0);
}
printf("one client over\n");
close(c);
//pthread_exit(NULL);
}
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,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));
assert(res != -1);
listen(sockfd,5);//
while(1)
{
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c < 0)
{
continue;
}
pthread_t id;
pthread_create(&id,NULL,thread_fun,(void*)c);
//char *s = NULL;
//pthread_join(id,(void**)&s);
//break;
}
exit(0);
}
在这个代码中 , 最开始遇到的问题就是pthread_join() [等待一个线程的结束]的使用 , 如果在主函数中加入 , 当多个客户端向服务器发起连接的时候 , 只能启动一个线程 , 其他只会完成三次连接后,进入等待队列.不能够实现多个客户端向服务器发送信息.
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
int sockfd1 = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd1 != -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(sockfd1,(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(sockfd1,buff,strlen(buff),0);
memset(buff,0,128);
recv(sockfd1,buff,127,0);
printf("buff=%s\n",buff);
}
close(sockfd1);
return 0;
}
从运行结果来 , 看能够实现多线程网络编程.至于结果显示的最后一个字符为空 , 是因为在输入时加了空格 ; 而服务器端返回的ok数目与客户端发送的字符数不尽相同 ,这是由于存放消息的接收缓冲区中还存在数据(此时接收缓冲区有多少数据就会向客户端返回多少个ok(注:每两个字节一个ok));其实这些都可以忽略 , 最主要看的是多线程的实现.
客户端的代码与之前的相同
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
int sockfd1 = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd1 != -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(sockfd1,(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(sockfd1,buff,strlen(buff),0);
memset(buff,0,128);
recv(sockfd1,buff,127,0);
printf("buff=%s\n",buff);
}
close(sockfd1);
return 0;
}
多进程的实现 , 要用fork()来实现 . 同多线程一样, 也是在accept之后 , 对每个客户端创建子进程.
以下是实现代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
void sig_fun(int sig)
{
/*
当瞬间接受多个信号 , 服务器会当作是一个进程 , 造成崩溃.
解决方法 : 使用 waitpid() 来代替 wait()
*/
int val = 0;
wait(&val);
}
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,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));
assert(res != -1);
listen(sockfd,5);//
//signal(SIGCHLD,sig_fun);//使用信号 只有在子进程结束之后父进程才会收到信号 父进程才会执行wait()函数
while(1)
{
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c < 0)
{
continue;
}
pid_t pid = fork();
if(pid ==-1)
{
close(c);
continue;
}
if(pid ==0)
{
while(1)
{
char buff[128] = {0};
int n = recv(c,buff,127,0);
if(n <= 0)
{
break;
}
printf("buff=%s\n",buff);
send(c,"ok",2,0);
}
close(c);
printf("one client over\n");
exit(0);
}
//此时的c已经++ , 在 父 子 进程都可以对c(未++)的进行操作 会造成崩溃
//要在父进程中手动关闭
close(c);
}
exit(0);
}
在代码中值得关注的就是父进程为什么要close( c )?
在没有创建子进程之前 , 套接字描述符 c 就存在 , 当子进程创建成功时 , c的值就会++(先记作大写C) ;但此时的子进程 和父进程 是都可以对c进行操作的 , 如果代码编写错误就造成程序的崩溃 ; 所以要在子进程创建成功之后 就要手动关闭 c.
如果程序这样写 , 就会出现问题 , 就是在子进程结束之后 , 并没有对子进程进行处理 , 会造成僵死进程
如果在父进程中使用wait() , 就会使得只能一个客户端连入 ; 达不到我们所期望的那样 ; 此时就要引入信号机制 .
#include
signal(SIGCHLD,sig_fun);
使用信号 只有在子进程结束之后父进程才会收到信号 父进程才会执行wait()函数.
这样就能解决僵死进程的出现.
但是这样就会出现一个新的问题 , 当瞬间接受多个信号 , 服务器会当作是一个进程 , 造成崩溃.
解决方法 : 使用 waitpid() 来代替 wait() ; waitpid()会暂时停止目前进程的执行 ,直到有信号来到或子进程结束。这里就不多赘述了.
#include
#include
#include
#include
#include
#include
#include
#include
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));
assert(res != -1);
while(1)
{
int len = sizeof(caddr);
char buff[128] = {0};
int c = recvfrom(sockfd,buff,127,0,(struct sockaddr*)&caddr,&len);
if(c == -1)
{
continue;
}
printf("port:%d,buff=%s\n",htons(caddr.sin_port),buff);
sendto(sockfd,"ok",2,0,(struct sockaddr*)&caddr,sizeof(caddr));
}
exit(0);
}
UDP 是User Datagram Protocol的简称 , 中文名是用户数据报协议 , 在网络中它与TCP协议一样用于处理数据包 , 是一种无连接的 , 不可靠的 协议。
UDP的实现与TCP基本相似 ,以下是UPD的编写流程.
头文件:#include <sys/types.h> #include <sys/socket.h>
定义函数:int sendto(int s, const void * msg, int len, unsigned int flags, const struct sockaddr * to, int tolen);
函数说明: sendto() 用来将数据由指定的socket 传给对方主机.
参数说明 :
s : 为已建好连线的socket(socket描述符) , 如果利用UDP协议则不需经过连线操作.
msg : 指向欲连线的数据内容.
flags : 一般设0, 详细描述请参考send().
to : 用来指定欲传送的网络地址, 结构sockaddr 请参考bind().
tolen : 为sockaddr 的结果长度.
sendto()返回值: 成功则返回实际传送出去的字符数, 失败返回-1, 错误原因存于errno 中.
头文件:#include <sys/types.h> #include <sys/socket.h>
定义函数:int recvfrom(int s, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);
函数说明: recvfrom()是从指定地址接收UDP数据报。
参数说明 :
s : socket描述符。
buf : UDP数据报缓存地址。
len : UDP数据报长度。
flags :该参数一般为0。
from : recvfrom()函数参数,struct sockaddr 类型,指明UDP数据从哪里收。
fromlen : recvfrom()函数参数,struct sockaddr_in类型,指明从哪里接收UDP数据报。
recvfrom()返回值: 对于recvfrom()函数,成功则返回接收到的字符数,失败则返回-1,错误原因存于errno中。
#include
#include
#include
#include
#include
#include
#include
#include
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");
while(1)
{
char buff[128] = {0};
printf("input:\n");
fgets(buff,128,stdin);
sendto(sockfd,buff,strlen(buff),0,(struct sockaddr*)&saddr,sizeof(saddr));
int len = sizeof(saddr);
memset(buff,0,128);
recvfrom(sockfd,buff,127,0,(struct sockaddr*)&saddr,&len);
printf("buff=%s\n",buff);
}
close(sockfd);
}
#include
#include
#include
#include
#include
#include
#include
#include
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));
assert(res != -1);
while(1)
{
int len = sizeof(caddr);
char buff[128] = {0};
int c = recvfrom(sockfd,buff,127,0,(struct sockaddr*)&caddr,&len);
if(c == -1)
{
continue;
}
printf("port:%d,buff=%s\n",htons(caddr.sin_port),buff);
sendto(sockfd,"ok",2,0,(struct sockaddr*)&caddr,sizeof(caddr));
}
exit(0);
}
注意 : udp 是无连接的 只要你发 我就收
问题 : 在服务器端的代码中 , int c = recvfrom(sockfd,buff,127,0,(struct sockaddr*)&caddr,&len);
如果将每次接收的字符由127 , 改为1 ; 那么在客户端发送"abc" , 会显示"a";再发送"123" , 会显示什么 , 是 “b” ?还是 “1” ?
解释 : recvfrom 一次没读完 , 剩下的就丢失. 所以要设置的足够大 , 能够一次性全部接收