编写客户/服务器
成都创新互联专注于昌乐网站建设服务及定制,我们拥有丰富的企业做网站经验。 热诚为您提供昌乐营销型网站建设,昌乐网站制作、昌乐网页设计、昌乐网站官网定制、
微信小程序定制开发服务,打造昌乐网络公司原创品牌,更为您提供
昌乐网站排名全网营销落地服务。
1.编写单进程客户/服务器(Version1)
代码清单:
tcp_servet
#include
#include
#include
#include
#include
#include
#include
static void usage(const char* proc)
{
printf("Usage: %s [local_ip] [local_port]",proc);//提示输入出错,应该输入./tcp_servet ip地址 端口号(运行服务器)
}
int startup(const char* ip,int port)//创建套接字
{
int sock = socket(AF_INET,SOCK_STREAM,0);//socket()创建套接字函数
if(sock<0)
{
perror("socket");//提示创建套接字失败
exit(2);
}
//socket的返回值只是一个文件描述符,为了进行网络通信,我们还需将其与服务器 ip地址和端口号进行绑定
struct sockaddr_in local;//结构体struct sockaddr_in是struct sockaddr的具体化
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = inet_addr(ip);
if(bind(sock,(struct sockaddr*) &local,sizeof(local))<0){//bind(绑定函数
perror("bind");
exit(3);
}
//监听
if(listen(sock,10)<0)//服务器24小时监听,等待连接请求
{
perror("listen");
exit(4);
}
return sock;
}
//Version 1
//./tcp_servet 127.0.0.1 8080 本地环回
int main(int argc,char* argv[])
{
if(argc!=3){
usage(argv[0]);
return 1;
}
int listen_sock = startup(argv[1],atoi(argv[2]));//create socket
int new_sock;
while(1){
struct sockaddr_in client;
socklen_t len = sizeof(client);
new_sock = accept(listen_sock,(struct sockaddr*)&client ,&len);//accept创建新的socket,用来处理客户段请求,listen_sock只负责监听
if(new_sock<0)
{
perror("accept");
exit(5); continue;
}
//成功获取到客户端,打印客户端的 ip和端口号,accept的第二个参数是输出型参数,由它可得客户端的ip和端口号
printf("get an new client: %s, %d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
break;
}
//客户端和服务器端的数据传输
char buf[1024];
while(1){
int r = read(new_sock,buf,sizeof(buf)-1);//从网络中读取数据(客户端发送数据)放入buf中;
if(r<0){//读取失败
perror("read");
close(new_sock);
break;
}else if(r==0){//客户端已关闭
close(new_sock);
printf("client qiuit...\n");
break;
}
//读取成功
buf[r] = 0;
printf("clinet: %s\n",buf);
write(new_sock,buf,strlen(buf));//回送数据
}
return 0;
}
tcp_client.c客户端
#include
#include
#include
#include
#include
#include
#include
void static Usage(const char* proc)//提示打印格式
{
printf("Usage: %s [local_ip] [local_port]\n",proc);//运行客户端./tcp_servet 服务器ip 服务器端口号
}
//./tcp_clinet servet_ip servet_port
int main(int argc,char* argv[])
{
if(argc!=3){
Usage(argv[1]);
exit(1);
}
int sock = socket(AF_INET,SOCK_STREAM,0);//创建套接字
if(sock < 0 ){
perror("socket");
exit(2);
}
//客户端socket不绑定
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));//htons将本地端口号转为网络端口号
server.sin_addr.s_addr = inet_addr(argv[1]); //给数据结构成员赋值
//connect()链接服务器
if(connect(sock,(struct sockaddr*)&server,sizeof(server))<0){
perror("connect");
exit(3);
}
//客户端发送数据
char buf[1024];
while(1){
printf("Please Enter$");
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf)-1);//从标准输入读数据到buf
if(s>0){//读取成功
buf[s-1] = 0;
write(sock,buf,strlen(buf));//将数据写到网络中
s = read(sock,buf,sizeof(buf)-1);//读取回显数据
if(s>0){
buf[s] = 0;
printf("servetr echo#:%s\n",buf);
}
}
return 0;
}
注意要点:
上述的服务器地址采用127.0.0.1,故该服务器没有通过网络通信,还是一直监听所在主机上的客户链接请求,这是本地环回;
该客户/服务器是一个单进程的服务器,一次只能链接一个客户请求,因为我们在收到一个客户请求后,执行完客户端的请求才接受下一个请求的链接
客户端的套接字可以绑定也可以不绑定,但一般不进行绑定,因为一点绑定服务器的ip,那么若别的客户端想链接,链接不上
涉及函数以及命令
创建socket
domain表示域,ipv4为AF_INET;
type表示类型,SOCK_STREAM(面向字节流)
protocal表示协议,单个通信协议情况下为0
bind绑定
sockfd : socket()的返回值,一个文件描述符
addr:一个struct sockaddr_in 的结构体
struct in_addr结构体成员是s_addr,表示ip地址;
addrlen:结构体大小
accept创建新的socket
addr是一个输出型参数,输出的是客户端的struct sockaddr_in 的内容
addrlen是一个输入输出型参数
ip转换
本地端口号与网络端口号的互换
监听
backlog : 是操作系统底层连接队列,不能设置过大;
connect函数(客户端连接服务器)
2.多进程服务器编写
代码清单:
tcp_servet.c
#include
#include
#include
#include
#include
#include
#include
//Version2
void static usage(const char* proc)
{
printf("Usage: %s [local_ip] [local_port]\n",proc);
}
int startup(char* _ip,int _port)
{
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock<0){
perror("socket");
exit(2);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = inet_addr(_ip);
if(bind(sock,(struct sockaddr*) &local,sizeof(local))<0){
perror("bind");
exit(3);
}
if(listen(sock,10)<0){
perror("listen");
exit(4);
}
return sock;
}
//./tcp_servet local_ip local_port
int main(int argc,char* argv[]){
if(argc!=3){
usage(argv[0]);
return 1;
}
int listen_sock = startup(argv[1],atoi(argv[2]));
int new_sock;
while(1){
struct sockaddr_in client;
socklen_t len = sizeof(client);
new_sock = accept(listen_sock,(struct sockaddr*) &client,&len);
if(new_sock < 0){
perror("accept");
exit(5);
continue;
}
printf("get an new client:%s,%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
break;
}
//创建了一个子进程代替我们处理客户端的请求---与上述单进程的不同之处
pid_t id = fork();
if(id<0){
perror("fork");
exit(6);
}else if(id==0){//child
close(listen_sock);//关闭不必要的文件
if(fork > 0){//两次fork
exit(7);
}
char buf[1024];
while(1){
int r= read(new_sock);
if(r<0){
perror("read");
close(new_sock);
break;
}else if(r==0){
close(new_sock);
printf("clinet quit...\n");
break;
}
buf[r] = 0;
printf("clien: %S\n",buf);
write(new_sock,buf,strlen(buf));
close(new_sock);
}
}else{//father
close(new_sock);//父进程继续负责监听客户请求,必须关闭new_sock文件
}
}
注意要点:
与单进程的唯一不同的是, fork了一个子进程,每次客户端的请求交给子进程完成,父进程只负责监听 ;
两次 fork,为了能够让父进程只负责监听,而不阻塞式的等待子进程的退出,我们采用两次fork,此时存在两个进程,两个进程存在爷孙关系,“孙子”进程此时由于父进程已经退出,所以它是孤儿进程,由 init进程回收,与此刻的父进程没有关系
pid_t id = fork();
if(id<0){
perror("fork");
exit(6);
}else if(id==0){//child
close(listen_sock);//关闭不必要的文件
if(fork > 0){//两次fork,第二次创建完子进程后该父进程退出
exit(7);
}
父进程必须关闭new_sock文件,因为子进程拷贝了父进程的文件描述符表,两者表是一样的,若不关闭,每次客户端发送来一个新的连接,就要在父进程的文件描述符表中打开一个文件,这样浪费资源,而且很快会用完;(子进程处理完信息后退出时关闭了文件描述符表,而父进程一直处于运行状态,所以必须主动关闭)
3.多线程服务器的编写
代码: