1、socket

      每一条TCP连接两个端点,TCP连接的端点交错socket

      socket=(IP地址:端口号)———>标示了网络上唯一的一个进程

      每一条TCP连接被两个socket(即socket pair)唯一的确定。

       TCP连接={socket1,socket2}={{IP1,port1},{IP2,port2}}

2、网络字节序 

1)大端:大端放在该值的起始地址;一个数据的高字节放在起始地址

2)小端:小端放在该值的起始地址;一个数据的低字节放在起始地址

3)数组:数组元素是按照元素个数开辟完空间,将第一个元素放在低地址,而第一个元素如果是大端,就将其高字节放在元素的起始地址处

    wKioL1eZpBnyRUWGAADi8omVFpI700.png

4)网络字节序

     网络字节序是大端的,读的时候认为读到的第一个字节是高字节;所以小端要转换成大端

     wKiom1eZpCzi0LV3AADVVQHM80Q722.png

    发数据:从低地址——>高地址          ;收数据:从低地址——>高地址

                    主机字节序——网络字节序                    网络字节序——主机字节序

3、socket地址的数据类型

wKioL1eZpG2yCszDAAC1q1-xjB8729.png

各种socket地址结构体的开头都是相同的,前16位表示整个结构体的长度(并不是所有UNIX的实现 都有长度字段,如Linux就没有),后16位表示地址类型。


4、TCP

   wKioL1eZpf3A8lN0AAMhbrib52U296.png

几个需要注意的点:

          1)socket中的数据格式是:SOCK_STREAM

          2)   bind之前需要将容器struct sockaddr_in 进行初始化:

                                                  端口号:将主机序列转成网络序列;

                                                  IP地址:字符串转成×××

          3)int listen(sockfd,backlog)    需要了解backlog是什么意思

               listen状态下可以监听多个client,所以可以backlog个client处于连接等待状态,超过    

               backlog个的就忽略

          4)accept()用于被动等待连接,其中有两个参数需要注意

               int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen)

               addr:是一个传出参数,该函数返回时,将client的端口号和IP地址填充进去如果

                        addr=NULL,表示不关心addr的地址

               addrlen:是一个传入传出参数,进去的时候是定义的addr结构体的大小,传出的是实

                             际client的addr 的大小(有可能实际的小于定义的)

           5)client端不需要bind函数去绑定,也不建议bind,但是常规上是可以bind的

          6)connect连接的时候填充的是server的地址

代码1:单server——单client

1)server.c

思考:在客户端的read函数要有正确的处理

      如果read的返回值:大于0,说明读到数据了,就打印在屏幕上;

                                      等于0,说明服务器没有再给客户端发信息,要关闭客户端

    (下面说明这种情况,服务器先主动关闭后,向客户端发送FIN,客户端发送ACK相应,然后客户端继续发信息,服务器会以RST响应,这个时候如果客户端继续调用read,由于客户端已经收到了服务器的FIN,所以read会返回0)

     如果客户端对于read的返回值没有进行处理,继续发信息给服务器,这个时候如果客户端在已经收到RST的情况下,想要调用write函数,内核会发送一个SIGPIPE信号,来终止进程,所以这个信号也需要捕获处理

      如果在read之前,服务器就崩溃了,read的会阻塞等待,那么它会等很长时间才会放弃连接


 #include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
const short _PORT=8080;
#define _IP "127.0.0.1" 
int main()
{
  //1.create socket
  int listenfd=socket(AF_INET,SOCK_STREAM,0);
  if(listenfd<0)
  {
    perror("socket");
    return 1;
  }
  //2.1initial sockaddr_in
  struct sockaddr_in local;
  local.sin_family=AF_INET;
  local.sin_port=htons(_PORT);
  local.sin_addr.s_addr=inet_addr(_IP);
  if(bind(listenfd,(struct sockaddr*)&local,sizeof(local))<0)
  {
    perror("bind");
    return 2;
  }
  int ret=listen(listenfd,5);
  if(ret<0)
  {
    perror("listen");
    return 3;
  }
  while(1)
  {
    struct sockaddr_in peer;
    socklen_t len=sizeof(peer);
    int fd=accept(listenfd,(struct sockaddr *)&peer,&len);
    if(fd<0)
    {
      perror("accept");
      return 4;
    }
    else{
        char buf[1024];
        while(1)
        {
          memset(buf,'\0',1024);
          ssize_t _s=read(fd,buf,sizeof(buf)-1);
          if(_s>0)
          {
		//printf("client# %s",buf);
            buf[_s]='\0';
            write(fd,buf,sizeof(buf)-1);
 	      printf("client# %s",buf);
           // write(fd,buf,sizeof(buf)-1);
          }
          else if(_s==0)
          {
            printf("read done ...\n");
            break;
          }
          else{
            break;
          }
        }
        close(fd);
        break;
    }
    
  }
  return 0;
}

2)client.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
const short _PORT=8080;
const char *_IP="127.0.0.1";
int main()
{
  //1creat socket
  int fd=socket(AF_INET,SOCK_STREAM,0);
  if(fd<0)
  {
    perror("socket");
    return 1;
  }
  struct sockaddr_in remote;
  remote.sin_family=AF_INET;
  remote.sin_port=htons(_PORT);
  remote.sin_addr.s_addr=inet_addr(_IP);
  int ret=connect(fd,(struct sockaddr *)&remote,sizeof(remote));
  if(ret<0)
  {
    perror("connect");
    return 2;
  }
  char buf[1024];
  while(1)
  {
    memset(buf,'\0',sizeof(buf));
    printf("Please Enter:\n");
    ssize_t _s=read(0,buf,sizeof(buf)-1);
    if(_s>0)
    {
      buf[_s]='\0';
      write(fd,buf,strlen(buf));
      memset(buf,'\0',sizeof(buf));
      ssize_t _ss=read(fd,buf,sizeof(buf)-1);
      if(_ss>0)
      {
        buf[_ss]='\0';
        printf("%s",buf);
      }
      else
      {
        break;
      }
    }
  }
  return 0;
}

代码2:用fork子进程实现 单server——多client (client代码同上,需要修改的只有server端)

总结:

  (1)子进程可以关闭不必要的文件描述符或者释放其他资源,因为使用fork后,如果子进程不调用exec以使用新的进程空间的话,子进程会复制父进程的进程空间内容,包括数据段等(代码段是共享的,数据段等采用一种写时复制的策略来提高性能)。所以不必要的资源可以尽快释放。

    (2)由于子进程在退出后会成为僵尸进程,这些信息会遗留在父进程内,所以父进程注册所监听的信号及其处理函数(SIGCHLD是子进程退出时向父进程发送的信号,默认情况下是不采取任何动作,所以我们需要调用wait或者waitpid来彻底清除子进程。)较为常用的信号注册和信号处理可以在fork前调用,使得子进程也可以完成同样的功能。

    (3)最后父进程要关闭已经accept返回的socket文件描述符,因为父进程不处理相应活动,而是交由fork出来的子进程来处理。

    (4)使用fork的时候,要注意它对父子进程有执行顺序是不做任何限制的(vfork的话会先运行子进程),所以必要的时候可以让进程睡眠或者用消息唤醒。

   使用fork会有以下问题:

       1 fork是昂贵的,内存映像要从父进程拷贝到子进程,所有描述字要在子进程中复制等等,还有进程的上下文切换开销。

       2 fork子进程后,需要用 进程间通信IPC 在父子进程之间传递信息,特别是fork内的信息。

信号处理函数实现如下:
void sig_child(int signo)         //父进程对子进程结束的信号处理
{
 pid_t pid;
 int   stat;

 while( (pid=waitpid(-1,&stat,WNOHANG))>0)
 printf("child %d terminated\n",pid);

 return;
}
这样一来我们就可以应付子进程退出后的残留信息问题。注意这里使用waitpid,其中使用WNOHANG参数来防止父进程阻塞在wait上。



wKiom1eZtnzwhWHbAAByvR90zZo771.png

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
const short _PORT=8080;
#define _IP "127.0.0.1" 
int main()
{
  int listenfd=socket(AF_INET,SOCK_STREAM,0);
  if(listenfd<0)
  {
    perror("socket");
    return 1;
  }
  struct sockaddr_in local;
  local.sin_family=AF_INET;
  local.sin_port=htons(_PORT);
  local.sin_addr.s_addr=inet_addr(_IP);
  if(bind(listenfd,(struct sockaddr *)&local,sizeof(local))<0)
  {
    perror("bind");
    return 2;
  }
  if(listen(listenfd,5)<0)
  {
    perror("listen");
    return 3;
  }
  while(1)
  {
    struct sockaddr_in peer;
    socklen_t len=sizeof(peer);
    int fd=accept(listenfd,(struct sockaddr*)&peer,&len);
    int id=fork();
    if(id==0)
    {
      close(listenfd);
      while(1)
      {
        char buf[1024];
        memset(buf,'\0',sizeof(buf));
        ssize_t _s=read(fd,buf,sizeof(buf)-1);
        if(_s>0)
        {
            buf[_s]='\0';
            printf("client#%s\n",buf);
            write(fd,buf,sizeof(buf)-1);
        }
      }
      close(fd);
      exit(0);
    }else{
      close(fd);
    
    }
  }
  return 0;
}

代码3:用线程实现 单server——多client(client代码同上)

wKiom1eZyRLwgo2aAAB9AjCUEPo731.png

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
const short _PORT=8080;
#define _IP "127.0.0.1"
void *run(void *arg) 
{
  char buf[1024];
  int fd=(int)arg;
  while(1)
  {
    memset(buf,'\0',sizeof(buf));
    ssize_t _s=read(fd,buf,sizeof(buf)-1);
    if(_s>0)
    {
      buf[_s]='\0';
      write(fd,buf,strlen(buf));
      printf("client# %s",buf);
    }
    else if(_s==0)
    {
      printf("read done...\n");
      close(fd);
      break;
    }
    else
        break;
  }
  exit(0);
}
int main()
{
  int listenfd=socket(AF_INET,SOCK_STREAM,0);
  if(listenfd<0)
  {
    perror("socket");
    return 1;
  }
  struct sockaddr_in local;
  local.sin_family=AF_INET;
  local.sin_port=htons(_PORT);
  local.sin_addr.s_addr=inet_addr(_IP);
  if(bind(listenfd,(struct sockaddr *)&local,sizeof(local))<0)
  {
    perror("bind");
    return 2;
  }
  if(listen(listenfd,5)<0)
  {
    perror("listen");
    return 3;
  }
  while(1)
  {
    struct sockaddr_in peer;
    socklen_t len=sizeof(peer);
    int fd=accept(listenfd,(struct sockaddr *)&peer,&len);
    if(fd>0)
    {
      pthread_t id;
     // int ret=pthread_create(&id,NULL,run,(void *)fd);
      int ret=pthread_create(&id,NULL,run,(void*)fd);
      if(ret<0)
      {
         perror("pthread_create");
      }
      //pthread_detach(id);
    }
  }
  close(listenfd);
  return 0;
}

4、关于bind:Address Already in use的解决方案及分析

当./server跑起来./client跑起来

wKioL1eZqEzwYmtJAAApDFGZ8nc375.png

wKiom1eZqEzi5rmRAAA2aHnKIPo272.png

情况是这个的用netstat -nltp查看tcp服务

wKioL1eZqH7A8mQnAABm_sXQTP4795.png

这个服务就是server端的服务

这个时候我们在./server那个终端窗口下输入 ctl+c

这个时候./server终止属于四次挥手中主动关闭的那一个人,意味着他在发送最后一个ACK后还要经历一个time_wait的状态等待2MSL。

1)如果在关闭之后在./server关闭./client之后立即运行就会发现这个一个现象-------TIME_WAIT

这里的原因就是time_wait:需要等待2msl的时间

2)如果只关闭了./server之后在输入./server (./client没有被终止)------FIN_WAIT2

这里的原因是只完成了半关闭,./server一直在等./client关闭

wKiom1eZqTTz5vJlAAA4GqImjtY831.png

他说绑定的端口已经被占用了,那是哪个端口不能用呢,就是第一次socket绑定的8080不能用;

其实我们想做到的是关闭accept这个连接,对于listen的socket并不想关闭;也就是说我们只不过是不想连接了但是我们还是需要继续监听的,这两个行为占用的端口号相同,但是IP地址不同,建立连接的时候需要具体的IP地址,而监听的时候用的IP地址是网络地址。

这里用一个函数可以做到 setsockopt()设置socket描述符的 选项SO_REUSEADDR1,表 允许创建端 号相同但IP地址不同的多个socket描述符。