客户端
#include<stdio.h>
#include<stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, const char *argv[])
{
if(argc != 3) //判断终端是否传入目标服务器ip和端口号
{
fprintf(stderr,"请输入:%s [ip] [port]\n",argv[0]);//提示用户输入传入目标服务器IP和端口号
return -1;
}
char buf[128];//用来储存发送数据的数组
int sfd;//定义套接字
int set;
ssize_t send_bytes;
struct sockaddr_in src;//地址结构体;
socklen_t len=sizeof(src);
sfd=socket(AF_INET,SOCK_STREAM,0);//创建套接字
if(sfd<0)
{
perror("socket");
return -1;
}
src.sin_family=AF_INET;//ip为IPv4地址
src.sin_port=htons(atoi(argv[2]));//目标服务器端口号(从终端传入的是字符串,我们先用atio函数将字符串转换从整型,然后用htons函数将整型转换成网络字节序)
src.sin_addr.s_addr=inet_addr(argv[1]); //目标ip(用inet_addr函数将ip字符串转化成网络字节序)
ret=connect(sfd,(const struct sockaddr *)&src,len);//连接目标服务器
if(ret<0)
{
perror("connect");
return -1;
}
printf("连接成功\n");
while(1)
{
memset(buf,0,sizeof(buf));//数组buf清0
fgets(buf,sizeof(buf),stdin);//从键盘写入数据存入数组buf
buf[strlen(buf)-1]=0;//fgets会将回车也读入,所以我们要删掉回车
send_bytes=send(sfd,buf,strlen(buf),0);//发送buf里的数据给目标服务器
if(send_bytes<0)
{
perror("send");
return -1;
}
}
return 0;
}
服务器用进程实现
#include<stdio.h>
#include<stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#define BACKLOG 10;
void sig_handler(int signal)
{
wait(NULL);//回收僵尸子进程
}
int main(int argc, const char *argv[])
{
if(argc != 3)//判断终端是否传入ip和端口号
{
fprintf(stderr,"请输入:%s [ip] [port]\n",argv[0]);
return -1;
}
char buf[128];//存放收到信息的数组
int sfd,cfd,ret;
ssize_t recv_bytes;
struct sockaddr_in src,cli;//地址结构体
socklen_t clilen=sizeof(cli);
sfd=socket(AF_INET,SOCK_STREAM,0);//创建套接字,第一个参数为协议族这里表示是IPv
4,第二参数为套接字类型这里为流式套接字
if(sfd<0)
{
perror("socket");
return -1;
}
//地址结构体填充 地址族 ip 端口号
src.sin_family=AF_INET;
src.sin_port=htons(atoi(argv[2]));
src.sin_addr.s_addr=inet_addr(argv[1]);
ret=bind(sfd,(const struct sockaddr *)&src,sizeof(src));//绑定套接字,服务器ip和端口号
if(ret<0)
{
perror("bind");
return -1;
}
ret=listen(sfd,BACKLOG);//将套接字设定为被动接听状态,监听客户端的连接请求,BACKLOG为未决队列的长度
if(ret<0)
{
perror("listen");
return -1;
}
if(signal(SIGCHLD,sig_handler)==SIG_ERR)//子进程状态改变会给父进程发送SIGCHLD信号,这里处理方式为自定义方式,进入sig_handler信号处理函数
{
perror("signal");
return -1;
}
pid_t pid;
while(1)
{
cfd=accept(sfd,(struct sockaddr *)&cli,&clilen);//接收客户端连接请求,产生新的套接字,用和于客户端通信,通过cli保存客户端ip地址和端口号
if(cfd<0)
{
perror("accpet");
return -1;
}
pid=fork();//每连接一个客户端就产生一个子进程和来接收该客户端发送的消息
if(pid<0)
{
perror("fork");
return -1;
}
if(pid>0)
{
}
if(pid==0)
{
close(sfd);
printf("ip:%s port:%d 已链接\n",inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));
while(1)
{
memset(buf,0,sizeof(buf));//将数组buf清0
recv_bytes=recv(cfd,buf,sizeof(buf),0);接收客户端的消息储存buf中
if(recv_bytes<0)
{
perror("recv");
return -1;
}else if(recv_bytes==0)//客户端退出时,退出循环
{
printf("ip:%s port:%d 已断开链接\n",inet_ntoa(cli.sin_addr),
break;
}
printf("ip:%s port:%d\n",inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));//打印客户端ip和端口号
printf("buf:%s\n",buf);//打印客户端发送的消息
}
close(cfd);//关闭用于和该客户端通信的套接字
exit(EXIT_SUCCESS);//当客户端退出死杀死该子进程
}
}
close(sfd);
close(cfd);
return 0;
}
不足 :每个字进程都需要单独分配内存空间,客户端越多,则进程越多,就会消耗更多资源。
进程间协同需要进程间通讯。
服务器用线程实现
使用多线程可以节约一定的资源,减少复杂的进程间的通讯 。
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#define BACKLOG 10
struct sockaddr_in cli;
void *thread_fun(void *arg)
{
int cfd=*(int *)arg;
char buf[128];
ssize_t recv_bytes,send_bytes;
struct sockaddr_in a=cli;
socklen_t len=sizeof(a);
printf("ip:%s port:%d 已链接\n",inet_ntoa(a.sin_addr),ntohs(a.sin_port));
while(1)
{
memset(buf,0,sizeof(buf));
recv_bytes=recv(cfd,buf,sizeof(buf),0);
if(recv_bytes<0)
{
perror("recv");
break;
}else if(recv_bytes==0)
{
printf("ip:%s port:%d 已断开链接\n",inet_ntoa(a.sin_addr),ntohs(a.sin_port));
break;
}
printf("ip:%s port:%d\n",inet_ntoa(a.sin_addr),ntohs(a.sin_port));
printf("buf:%s\n",buf);
}
close(cfd);
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
if(argc != 3)
{
fprintf(stderr,"请输入:%s [ip] [port]\n",argv[0]);
return -1;
}
pthread_t tid;
int sfd,cfd,ret;
ssize_t recv_bytes,send_bytes;
struct sockaddr_in src;
socklen_t clilen=sizeof(cli);
sfd=socket(AF_INET,SOCK_STREAM,0);
if(sfd<0)
{
perror("socket");
return -1;
}
src.sin_family=AF_INET;
src.sin_port=htons(atoi(argv[2]));
src.sin_addr.s_addr=inet_addr(argv[1]);
ret=bind(sfd,(const struct sockaddr *)&src,sizeof(src));
if(ret<0)
{
perror("bind");
return -1;
}
ret=listen(sfd,10);
if(ret<0)
{
perror("listen");
return -1;
}
while(1)
{
cfd=accept(sfd,(struct sockaddr *)&cli,&clilen);
if(cfd<0)
{
perror("accpet");
return -1;
}
ret=pthread_create(&tid,NULL,(void *)thread_fun,(void *)&cfd);/每当有客户端连接成功就创建一个线程来接收消息
if(ret<0)
{
perror("pthread_create");
return -1;
}
pthread_detach(tid);//子线程分离,自动释放资源
}
close(sfd);
close(cfd);
return 0;
}
服务器 select多路复用io实现
用单进程处理多个堵塞型io 占用资源开销较少
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#define BACKLOG 10
void sig_handler(int signum)//信号处理函数
{
pid_t pid;
pid = wait(NULL);//回收僵尸子进程
printf("child process is recycled successful, pid = %d\n",pid);
}
void recv_send(int cfd)
{
char buf[128] = {0};
ssize_t recv_bytes, send_bytes;
while(1)
{
memset(buf,0,sizeof(buf));
recv_bytes = recv(cfd, buf, sizeof(buf),0);//接收客户端的信息储存到buf中
if(recv_bytes == -1)
{
perror("recv");
break;
}else if(recv_bytes == 0)//客户端退出返回0,服务器这里也退出循环
{
printf("client shutdown\n");
break;
}
printf("buf: %s\n",buf);
send_bytes = send(cfd, buf, recv_bytes, 0);//将收到的数据原样返回给发送的客户端
if(send_bytes == -1)
{
perror("send");
break;
}
}
exit(EXIT_SUCCESS);
}
int main(int argc, char **argv)
{
int ret;
pid_t pid;
int sfd,cfd;
struct sockaddr_in src,cli;
socklen_t len = sizeof(src);
socklen_t addrlen = sizeof(cli);
if(signal(SIGCHLD, sig_handler) == SIG_ERR)//子进程状态改变,会给父进程发送SIGCHLD信号,这里处理方式为自定义方式 进入sig_handler信号处理函数
{
perror("");
return -1;
}
sfd = socket(AF_INET, SOCK_STREAM, 0);//创建套接字,第二个参数为流式套接字类型
if(sfd == -1)
{
perror("socket fail");
return -1;
}
src.sin_family = AF_INET;//服务器地址结构体填充,地址族,IP PORT
src.sin_port = htons(atoi(argv[2]));
src.sin_addr.s_addr = inet_addr(argv[1]);
ret = bind(sfd, (const struct sockaddr *)&src, len );//绑定套接字,服务器ip和port口号
if(ret == -1)
{
perror("bind");
return -1;
}
ret = listen(sfd, BACKLOG);//将套接字设定为被动监听状态,监听客户端的连接请求,10为未决队列长度
if(ret == -1)
{
perror("listen");
return -1;
}
while(1)
{
cfd = accept(sfd, (struct sockaddr *)&cli,&addrlen);//接收客户端连接请求,产生连接套接字,用于和客户端通信,通过cli保存客户端ip地址和port口号
if(cfd == -1)
{
perror("accept");
return -1;
}
printf("client IP: %s PORT: %d\n", inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));//打印客户端IP 和 PORT ,需要将网络字节序转换为主机字节序
pid = fork();
if(pid == -1)
{
perror("fork");
return -1;
}else if(pid == 0)
{
recv_send(cfd);
}else
{
}
}
return 0;
}