服务器:
1.打印出客户端的IP地址和端口号,并发送返回给客户端
2.可以同时连接多个客户端
实现思路:
第一部分:
1.创建socket()
2.给套接字命名bind
3.创建监听上限listen()
第二部分:
while(1){
cfd=accept(); //接收客户端的连接请求
pid=fork(); 创建子进程
if(pid==0){
//进入子进程,子进程需要完成如下事情:
1.子进程无须监听,关闭监听套接字
2.读取客户端发送过来的字母
3.完成小写转大写
4.将转换完成后的大写字母返回给客户端
}else if(pid>0){
//进入父进程
1.父进程无须与客户端建立连接,关闭连接套接字
2.父进程需要回收已经死亡的子进程
}
}
示意图说明:
client1连接服务器:
1.首先通过socket创建监听套接字(lfd);
2.利用lfd创建连接套接字cfd,然后服务器会创建一个子进程,用来与client1保持连接。
后续client2, … ,clientn来了以后服务器会分别创建子进程与它们保持连接。
服务器端的文件目录结构:
serverUtils.h
#ifndef _SERVERUTILS_H_
#define _SERVERUTILS_H_
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <signal.h>
#include <arpa/inet.h>
#include <strings.h>
#include <signal.h>
#include <ctype.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/mman.h>
//定义一个结构体用来存放客户端的地址结构
typedef struct IPAddr{
char IP[20];
int port;
}IPAddr;
//函数的定义
// 01创建套接字
int Socket(int domain,int type,int protocol);
//给套接字命名
int Bind(int socketfd,const struct sockaddr *addr,socklen_t addrlen);
//设置监听上限
int Listen(int socketfd,int backlog);
//阻塞等待
int Accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
//客户端连接服务器
int Connect(int fd,const struct sockaddr *sa, socklen_t salen);
int Close(int fd);
#endif
serverUtils.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include "serverUtils.h"
void perr_exit(const char* str)
{
perror(str);
exit(1);
}
//封装socket
int Socket(int domain,int type,int protocol)
{
int n;
if((n=socket(domain,type,protocol))<0)
perr_exit("socket error");
return n;
}
//封装bind
int Bind(int socketfd,const struct sockaddr *addr,socklen_t addrlen)
{
int n;
if((n=bind(socketfd,addr,addrlen))<0)
perr_exit("bind error");
return n;
}
//封装listen
int Listen(int sockfd,int backlog)
{
int n;
if((n=listen(sockfd,backlog))<0)
perr_exit("listen error");
return n;
}
//封装accept
int Accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen)
{
int n;
again:
if((n=accept(sockfd,addr,addrlen))<0){
if((errno==ECONNABORTED)||(errno==EINTR))
goto again;
else{
perr_exit("accept error");
}
}
return n;
}
//封装Connect
int Connect(int fd,const struct sockaddr *sa, socklen_t salen)
{
int n;
if((n=connect(fd,sa,salen))<0)
perr_exit("connect error");
}
int Close(int fd)
{
int n;
if((n=close(fd))<0)
perr_exit("close error");
return n;
}
server.c
#include "serverUtils.h"
//定义服务器的端口号
#define SERV_PORT 6666
void waitchild(int signo)
{
while((waitpid(0,NULL,WNOHANG)>0));
return;
}
int main()
{
//声明监听套接字和连接套接字
int lfd,cfd,ret,len;
pid_t pid;
char buf[4096];
int i;
//用于存储客户端的IP地址
char cli_IP[1024];
struct IPAddr clientAddr;
//定义客户端与服务器的地址结构
struct sockaddr_in serv_addr,cli_addr;
//客户端的地址结构长度
socklen_t cli_addr_len;
//01_创建一个监听socket,采用AF_INET(IPV4),SOCK_STREAM(TCP协议)
lfd=Socket(AF_INET,SOCK_STREAM,0);
int opt=1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
//初始化服务器的地址结构
bzero(&serv_addr,sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); //将主机字节序转换成网络字节序
serv_addr.sin_port=htons(SERV_PORT); //将主机字节序转换成网络字节序
//02_绑定服务器的地址结构、
Bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
//03_设置监听上限,此处是非阻塞的
Listen(lfd,64);
//获取客户端的地址结构大小
cli_addr_len=sizeof(cli_addr);
printf("Accepting connections...\n");
while(1)
{
//04_阻塞等待客户端的连接,参数1:监听套接字lfd,参数2:传入参数,参数3:传出参数
cfd=Accept(lfd,(struct sockaddr*)&cli_addr,&cli_addr_len);
printf("client IP:%s:Port:%d\n",inet_ntop(AF_INET,&cli_addr.sin_addr.s_addr,cli_IP,sizeof(cli_IP)),ntohs(cli_addr.sin_port));
//将客户端的地址结构存放到结构体内部
strncpy(clientAddr.IP,cli_IP,strlen(cli_IP));
clientAddr.port=ntohs(cli_addr.sin_port);
memset(buf,0,1024); //将buf清零
memcpy(buf,&clientAddr,sizeof(IPAddr));
write(cfd,buf,sizeof(buf)); //将客户端的地址结构发送给客户端
bzero(&clientAddr,0);
pid=fork();
if(pid==0){
//进入子进程
Close(lfd); //子进程无须监听,关闭监听套接字
break;
}else if(pid>0){
//进入父进程
//父进程首先要做的就是注册捕捉函数回收子进程
struct sigaction act;
act.sa_flags=0;
act.sa_handler=waitchild;
//父进程首先清空阻塞信号集
sigemptyset(&act.sa_mask);
ret=sigaction(SIGCHLD,&act,NULL);
Close(cfd); //父进程无须通信因此关闭与客户端的通信的套接字
continue;
}else
{
perror("fork error");
exit(1);
}
}
if(pid==0)
{
//进入子进程
while(1){
ret=read(cfd,buf,sizeof(buf));
if(ret==0){ //接收为0则关闭连接
Close(cfd);
exit(1);
}
for(i=0;i<ret;i++)
{
buf[i]=toupper(buf[i]);
}
ret=write(cfd,buf,ret); //写回客户端
if(ret==-1)
{
perror("writer error");
exit(1);
}
ret=write(STDOUT_FILENO,buf,ret);
if(ret==-1)
{
perror("writer error");
exit(1);
}
}
}
return 0;
}
makefile:
ALL:server
inc_path=./inc
server:./obj/server.o ./obj/serverUtils.o
gcc ./obj/server.o ./obj/serverUtils.o -o server
./obj/server.o:./src/server.c
gcc -c ./src/server.c -o ./obj/server.o -I $(inc_path)
./obj/serverUtils.o:./src/serverUtils.c
gcc -c ./src/serverUtils.c -o ./obj/serverUtils.o -I $(inc_path)
clean:
-rm -rf ./obj/*.o server
.PHONY: clean ALL
运行结果:
运行两个客户端程序连接到服务器如下所示:
查看利用ps -a查看此时的进程
server端有三个进程:
这是因为此时有两个客户端连接到了服务器,产生了两个子进程加上负责监听的父进程一共有三个进程。
客户端:
1.连接上服务器后同时接收服务器的发送给自己的Ip地址和端口号
2.发送小写字母给服务器并接收大写客户端返回的大写字母并输出在显示屏上。
客户端的文件目录结构:
代码:
clientUtils.h:
#ifndef _CLIENTUTILS_H_
#define _CLIENTUTILS_H_
//客户端
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <signal.h>
#include <arpa/inet.h>
#include <strings.h>
//定义一个结构体用来存放客户端的地址结构
typedef struct IPAddr{
char IP[20];
int port;
}IPAddr;
//函数的定义
// 01创建套接字
int Socket(int domain,int type,int protocol);
//给套接字命名
int Bind(int socketfd,const struct sockaddr *addr,socklen_t addrlen);
//设置监听上限
int Listen(int socketfd,int backlog);
//阻塞等待
int Accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
//客户端连接服务器
int Connect(int fd,const struct sockaddr *sa, socklen_t salen);
int Close(int fd);
#endif
clientUtils.c:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include "clientUtils.h"
void perr_exit(const char* str)
{
perror(str);
exit(1);
}
//封装socket
int Socket(int domain,int type,int protocol)
{
int n;
if((n=socket(domain,type,protocol))<0)
perr_exit("socket error");
return n;
}
//封装bind
int Bind(int socketfd,const struct sockaddr *addr,socklen_t addrlen)
{
int n;
if((n=bind(socketfd,addr,addrlen))<0)
perr_exit("bind error");
return n;
}
//封装listen
int Listen(int sockfd,int backlog)
{
int n;
if((n=listen(sockfd,backlog))<0)
perr_exit("listen error");
return n;
}
//封装accept
int Accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen)
{
int n;
again:
if((n=accept(sockfd,addr,addrlen))<0){
if((errno==ECONNABORTED)||(errno==EINTR))
goto again;
else{
perr_exit("accept error");
}
}
return n;
}
//封装Connect
int Connect(int fd,const struct sockaddr *sa, socklen_t salen)
{
int n;
if((n=connect(fd,sa,salen))<0)
perr_exit("connect error");
}
int Close(int fd)
{
int n;
if((n=close(fd))<0)
perr_exit("close error");
return n;
}
client.c
#include "clientUtils.h"
//定义服务器的IP地址
#define SERV_IP "127.0.0.1"
#define SERV_PORT 6666
#define MAXLINE 8192
int main(void)
{
//变量声明
pid_t pid; //客户端需要有两个进程一个用来读取一个用来发送
int nfd,ret,len;
struct sockaddr_in serv_addr;
char buf[4096];
//创建一个socket 指定IPV4 采用TCP来进行连接
nfd=socket(AF_INET,SOCK_STREAM,0);
//初始化地址结构
bzero(&serv_addr,sizeof(serv_addr)); //清零
serv_addr.sin_family=AF_INET; //IPV4
//将IP地址的字符串类型转换成网络字节序,参数3 是传出参数
inet_pton(AF_INET,SERV_IP,&serv_addr.sin_addr.s_addr);
//将端口号从主机字节序转化成网络字节序
serv_addr.sin_port=htons(SERV_PORT);
//根据地址结构连接指定服务器
Connect(nfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
//接收服务器的地址并写到显示屏幕上
struct IPAddr clientRecv;
char cli_IP[1024];
len=read(nfd,buf,sizeof(buf));
memcpy(&clientRecv,buf,len);
printf("MyIP:%s:Port:%d\n",clientRecv.IP,clientRecv.port);
while(fgets(buf,MAXLINE,stdin)!=NULL){
ret=write(nfd,buf,strlen(buf));
if(ret==-1)
{
perror("write error");
exit(1);
}
ret=read(nfd,buf,sizeof(buf));
if(ret==0)
{
printf("对方关闭连接\n");
exit(1);
}
int n=write(STDOUT_FILENO,buf,ret);
if(n==-1)
{
perror("write error");
exit(1);
}
}
//关闭连接
Close(nfd);
return 0;
}
makefile:
ALL:client
inc_path=./inc
client:./obj/client.o ./obj/clientUtils.o
gcc ./obj/client.o ./obj/clientUtils.o -o client
./obj/client.o:./src/client.c
gcc -c ./src/client.c -o ./obj/client.o -I $(inc_path)
./obj/clientUtils.o:./src/clientUtils.c
gcc -c ./src/clientUtils.c -o ./obj/clientUtils.o -I $(inc_path)
clean:
-rm -rf ./obj/*.o client
.PHONY: clean ALL