网络通信,实现客户端和服务器端的通信

service network restart:linux下重启网络
关闭防火墙:
Centos6(root): service iptables stop
Centos7(root): service firewaild stop
scp:跨主机之间的拷贝scp [email protected]:/home/test/client  ./(远端的client拷贝到当前路径下),密码为1
运行server
./client 114.115.172.211 9090
cp:本主机间的拷贝
0:表示机器上任何一个ip地址
127.0.0.1 环回ip,表示自己(同一台主机上的服务器)./server 0 9090        ./client 127.0.0.1 9090
accept默认是阻塞方式执行,如果当前没有客户端链接到服务器
,那么此时accept就会阻塞等待。一旦有客户端链接过来,accept才能返回

tcp存在一个服务器端只能右一个客户端链接,而udp不会,因为udp没有链接端口号
改进方式:采用多个执行流(多进程)(多线程)
1.多进程服务器端
# include<stdio.h>
# include<stdlib.h>
# include<sys/socket.h>
# include<unistd.h>
# include <netinet/in.h>
# include <arpa/inet.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
void ProcessConnect(int new_fd)
{
//1、创建进程
    int ret=fork();
    if(ret>0){
//父进程
//2、父进程能够快速的去执行下一次accept
//如果直接在此处wait,由于wait是阻塞等待子进程结束,才返回,那么就不能达到快速调用到第二次accept的效果
//假设此处我们使用非阻塞式的调用waitpid
//1、只调用一次waitpid,此时,相当于子进程还没有结束,waitpid快速返回了。虽然能够快速调用到第二次accept,
//但是由于waitpid并没有真正的回收子进程的资源,此时还是会出现僵尸进程
//2、如果采用轮询的方式调用waitpid,虽然保证没有僵尸进程,但是快速调用到accept又没有实现
//waitpid(-1,NULL,WNOHANG);
//更合理的方式,捕捉信号的方式,SIGCHLD
//或者直接忽略SIGCHLD
       close(new_fd);
       return;
    }
    else if(ret==0){
//子进程
//3、子进程能够循环的进行读写
     
    while(1){
      char buf[1024]={0};
      ssize_t read_size=read(new_fd,buf,sizeof(buf)-1);
      if(read_size<0){
         perror("read");
         return ;
      }
      if(read_size==0){
          printf("client %s:%d conect\n",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port);子进程关闭文件描述符
//
         close(new_fd);
         exit(0);
      }
      buf[read_size]='\0';
      printf("client %s:%d say:%s\n",inet_ntoa(peer.sin_addr,buf);
      write(new_fd,buf,strlen(buf));
    }
    }else{
      perror("fork");
    }
}
int main(int argc,char *argv[]){
    if(argc!=3){
       printf("Usage ./server[ip][port]\n");
       return 1;
    }
    signal(SIGCHLD,SIG_IGN);
    int fd=socket(AF_INET,SOCK_STEAM,0);
    if(fd<0){
       perror("socket");
       return 1;
    }
    sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_addr.s_addr=inet_addr(argv[1]);
    addr.sin_port=htons(atoi(argv[2]));
    int ret=bind(fd,(sockaddr *)&addr,sizeof(addr));
    if(ret<0){
       perror("bind");
       return 1;
    }
    ret=listen("fd,5);
    if(ret<0){
       perror("listen");
       return 1;
   }
 }
   while(1){
      sockaddr_in peer;
      socklen_t len=sizeof(peer);
//
      int new_fd=accept(fd,(sockaddr *)&peer,&len);
      if(new_fd<0){
         perror("accept");
         continue;
      }
//accept完成之后,创建子进程。让父进程能够快速的再次调用到accept,等待第二个客户端的连接,让子进程来处理当前这个连接的读写。
      ProcessConnect(new_fd);
   }
   return 0;
}
2.多线程服务器端
# include<stdio.h>
# include<stdlib.h>
# include<sys/socket.h>
# include<unistd.h>
# include <netinet/in.h>
# include <arpa/inet.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
void *ThreadEntry(void *ptr)
{
    Arg *arg=(Arg *)ptr;
    while(1){
       char buf[1024]={0};
       ssize_t read_size=read(arg->new_fd,buf,sizeof(buf)-1);
       if(read_size<0){
          perror("read");
          continue;
       }
       if(read_size==0){
          printf("client %s:%d diconnected",inet_ntoa(arg->peer.sin_addr),ntohs(arg->peer.sin_port));
       close(arg->new_fd);
       free(arg);
       return NULL;
       }
    buf[read_size]='\0';
      printf("client %s:%d say:%s\n",inet_nyoa(arg->peer.sin_addr),ntohs(arg->peer.sin_port),buf));
     write(arg->new_fd,buf,strlen(buf));
}
  return nULL;
  }
void ProcessConnect(int new_fd,sockaddr_in peer)
{
//创建一个线程
//让新线程进行循环读写
//主线程继续回头去调用accept
    Arg *arg=(Arg *)malloc(sizeof(Arg));
    arg->new_fd=new_fd;
    arg->peer=peer;
    pthread_t tid;
    pthread_create(&tid,NULL,ThreadEntry,(void *)arg);
    pthread_detach(tid);
}
int main(int argc,char *argv[]){
    if(argc!=3){
       printf("Usage ./server[ip][port]\n");
       return 1;
    }
    signal(SIGCHLD,SIG_IGN);
    int fd=socket(AF_INET,SOCK_STEAM,0);
    if(fd<0){
       perror("socket");
       return 1;
    }
    sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_addr.s_addr=inet_addr(argv[1]);
    addr.sin_port=htons(atoi(argv[2]));
    int ret=bind(fd,(sockaddr *)&addr,sizeof(addr));
    if(ret<0){
       perror("bind");
       return 1;
    }
    ret=listen("fd,5);
    if(ret<0){
       perror("listen");
       return 1;
   }
 }
   while(1){
      sockaddr_in peer;
      socklen_t len=sizeof(peer);
      int new_fd=accept(fd,(sockaddr *)&peer,&len);
      if(new_fd<0){
         perror("accept");
         continue;
      }
      ThreadEntry(arg);
   }
   return 0;
}

3、
先绑定在fork就会出现多个进程绑定在同一个端口号上
父进程直接return会产生僵尸进程,因为其没有获取子进程的退出状态
waitpid(-1,NULL,WNOHANG)
线程之间共享一个描述符表,所以只用close一次;而进程必须close多次
总结:
一、udp:
服务器端
1创建socket
2、绑定端口号
3、循环的读取数据
4、针对读取到的数据进行计算和chuli
5、把处理后的结果发送回客户端
客户端:
1、创建socket
2、给服务器发送请求
3、从服务器中读取结果
二、tcp
server:
1、创建socket
2、绑定端口号
3、把socket装换成被动模式(listen)
4、循环的进行accept
5、把accept返回的new_fd中读取客户端的请求
6、根据读取到的请求进行计算和处理
7、把处理后的结果发送给客户端
【多进程、多线程、IO多路复用】
client:
1、创建socket
2、和服务器建立连接
3、给服务器发送数据
4、从服务器读取返回的结果
5、和服务器断开连接

80为默认端口号
/服务器程序对应的根目录
假如我们实现一个http服务器,然后把/home/test/http/作为http服务器的根目录。此时相当于通过浏览器所能访问的文件就都局限在/home/test//http
目录之中。假设/home/test/http/index.html,那么我们的url就可以写成:http://[ip]:[port]/index.html

fidller抓包工具
get http请求 http请求的版本号
首行之后都为header报头:每个键值对之间用换行分隔
http请求
首行(方法url版本号)
若干行header(User-Agent,Cookie)
空行(body和header的分割)
body(这部分格式通常和url中的查询格式是类似的)
http响应:
首行(版本号 状态码 状态码描述)
若干行header
空行
body(往往就是网页的html)
http协议的服务器:都是基于tcp实现的
netstat anp| grep 端口号:查询防火墙
在vs下(gbk编码)一个汉字占两个字节;在国际上(UTF-8)变长编码,一个汉字几个字节与具体是怎样的字相关,常用的字短,不常用的字长。

猜你喜欢

转载自blog.csdn.net/xuruhua/article/details/80320498