网络编程之TCP与UDP

1、TCP与UDP的区别


Tcp是一种面向连接的,可靠的字节流服务。(设有数据包编号与差错控制机制。)
特点:
由于网络的复杂性,传输信息时,数据包可能会丢失,差错控制中的确认机制在接收到数据包是发送确认信息,若是数据包丢失,则回发数据包编号,让对方重新发送;
由于网络的复杂性,传输信息时有多种网络传送途径可以选择,数据包被接收的顺序与发送顺序不同,可以根据数据包的编号,将数据包重组。
优点:
网络连接是以点对点的形式,加上上述特点,保证了数据的安全性,数据包不会中途被劫。
缺点:耗费资源很多。


UDP是无连接的,不可靠的数据协议报。通讯双方发送数据后不知道对方是否已经收到数据,是否正常收到数据(没有类似TCP的差错控制和数据包编号机制)。
特点:通讯速度比较快,不需要TCP的三次握手。
1)多播:
与发送主机的子网相同的主机,都会在自己的端口收到主机发出来的UDP消息(发送到(包括本机)所有在这个网络地址的主机上,例如192.168.0.255,则在192.168.0.的主机都会收到)。消息会被复制并发到每个主机的网卡上去,网卡收到消息后提交给操作系统去处理,操作系统发现有程序在端口接收UDP数据则把消息转给相应的程序去处理,如果没有程序接收来自该端口的UDP消息,则操作系统丢弃该消息。
2)单播:
则消息只会从主机发到特定的主机上,主机的网卡收到消息后转给操作系统去处理,操作系统再把此消息转给相应程序去处理,如果没有程序处理就丢弃该包。
3)组播:
则消息只会从主机发到加入了规定组的主机的端口。象广播一样,组播消息一样会被复制发到网络所有主机的网卡上,但只有宣布加入这个组的主机的网卡才会把数据提交给操作系统去处理。如果没有加入组,则网卡直接将数据丢弃。要想接收组播消息的主机必须运行命令加入组。



2、TCP和UDP编程

基本模式如图:



(1)UDP广播

在UDP方式下,将目标地址设为:255.255.255.255 或 192.168.1.255(192.168.1网段内),端口号配置正确,就可以发送广播。
客户端:
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<errno.h>
#include <pthread.h>

#define RET_OK 0
#define RET_ERR -1

#define LISTEN_QUEUE_NUM 5
#define BUFFER_SIZE 256

#define ECHO_PORT 2029

int sockfd;
struct sockaddr_in servaddr,recvaddr;
pthread_t t1;

void rev()
{
    int ret;
    char buffer[BUFFER_SIZE];
    uint32_t len=sizeof(recvaddr);
    while(1)
    {
        if((ret=recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&recvaddr,&len))<0)//监听地址中的socket读事件
        {
            perror("ERROR reading from socket");
            break;
        }
        buffer[ret]=0;
        printf("[form:%s:%u]%s\n",inet_ntoa(recvaddr.sin_addr),ntohs(recvaddr.sin_port), buffer);
    }
}

int main(int argc,char **argv)
{
	int opt=1;
	int ret=RET_OK;
	char buffer[BUFFER_SIZE];
	struct hostent *server;
	
	if(argc<2)
	{
	    fprintf(stderr,"usage &s hostname\n",argv[0]);
	    return RET_ERR;
	}
	
	if((server=gethostbyname(argv[1]))==NULL)
	{
	    herror("gethostbyname.");
	    return RET_ERR;
	}
	
	if((sockfd=socket(AF_INET,SOCK_DGRAM,0))<0)
	{
	    herror("ERROR opening socket");
	    return RET_ERR;
	}
	
	//设置广播套接字
	if((setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&opt,sizeof(opt)))<0)
	{
	    perror("ERROR setsockopt");
	    goto failed;
	}
	
	memset(&servaddr,0,sizeof(servaddr));//服务器地址
	
	servaddr.sin_family=AF_INET;//设为IPv4
	servaddr.sin_addr.s_addr=*(uint32_t *)server->h_addr;//地址转换,32位大端字序,或者INADDR_BROADCAST
	servaddr.sin_port=htons((uint16_t)ECHO_PORT);//设定端口,16位大端字序转换
	
	pthread_create(&t1, NULL, (void*)rev, NULL);
	
	while(1)//不断发送数据到指定地址端口
	{
	    printf("Enter the message : ");
	    if(fgets(buffer,sizeof(buffer)-1,stdin)==NULL)break;
	    if((ret=sendto(sockfd,buffer,strlen(buffer)+1,0,(struct sockaddr*)&servaddr,sizeof(servaddr)))<0)
	    {
	        perror("ERROR writing to socket");
	        break;
	    }
	    
	}
	
failed:
	close(sockfd);
	return ret<0?RET_ERR:RET_OK;
}



服务器端:
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<errno.h>


#define RET_OK 0
#define RET_ERR -1


#define LISTEN_QUEUE_NUM 5
#define BUFFER_SIZE 256

#define ECHO_PORT 2029

int main(int agrc,char **argv)
{
	int sockfd,opt=1;
	uint32_t len;
	struct sockaddr_in cliaddr;
	uint8_t buffer[BUFFER_SIZE];
	int ret=RET_OK;
	
	if((sockfd=socket(AF_INET,SOCK_DGRAM,0))<0)
	{
	  perror("ERROR opening socket");
	  return RET_ERR;
	}
	
	//SO_REUSEADDR允许重用本地址
	if((ret=setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)))<0)
	{
	  perror("ERROR setsockopt");
	  goto failed;
	}
	
	memset(&cliaddr,0,sizeof(cliaddr));
	cliaddr.sin_family=AF_INET;
	cliaddr.sin_addr.s_addr=INADDR_ANY;//监听所有地址发来的指定端口
	cliaddr.sin_port=htons(ECHO_PORT);
	
	if((ret=bind(sockfd,(struct sockaddr*)&cliaddr,sizeof(cliaddr)))<0)//绑定地址(udp不绑定也是可以的)
	{
	  perror("ERROR on binding");
	  goto failed;
	}
	
	do//不断接收数据并返回相同的数据
	{
	  len=sizeof(cliaddr);
	  if((ret=recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&cliaddr,&len))>0)
	  {
	      buffer[ret] = 0;
	      printf("[%s:%d]%s",inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port),buffer);
	      ret=sendto(sockfd,buffer,ret,0,(struct sockaddr*)&cliaddr,len);
	  }
	}while(ret>=0);
	
failed:
  close(sockfd);
  return 0;
}

运行过程:用ifconfig eth0 设置ip,并查看广播地址作为client.c的参数运行
运行结果:多个服务器可以同时接收来自客户端的信息


(2)UDP指定地址接受

UDP,client中使用connect指定接受地址。

指定接收数据的地址,其他IP地址的数据拒绝接收。


sendto,recvfrom可以用write和read代替.


客户端代码:

int sockfd;
struct sockaddr_in servaddr,recvaddr;
pthread_t t1;

void rev()
{
    int ret;
    char buffer[BUFFER_SIZE];
    uint32_t len=sizeof(recvaddr);
    while(1)
    {
        if((ret=read(sockfd,buffer,sizeof(buffer)-1,0))<0)
        {
            perror("ERROR reading from socket");
            break;
        }
        buffer[ret]=0;
        printf("[form:%s:%u]%s\n",inet_ntoa(recvaddr.sin_addr),ntohs(recvaddr.sin_port), buffer);
        //printf("Server echo message: %s\n",buffer);
    }
}


int main(int argc,char **argv)
{
  int opt=1;
  int ret=RET_OK;
  char buffer[BUFFER_SIZE];
  struct hostent *server;


  if(argc<2)
  {
      fprintf(stderr,"usage &s hostname\n",argv[0]);
      return RET_ERR;
  }
  
  if((server=gethostbyname(argv[1]))==NULL)
  {
      herror("gethostbyname.");
      return RET_ERR;
  }
  
  if((sockfd=socket(AF_INET,SOCK_DGRAM,0))<0)
  {
      herror("ERROR opening socket");
      return RET_ERR;
  }

  memset(&servaddr,0,sizeof(servaddr));//申请信息表空间

  servaddr.sin_family=AF_INET;//设为IPv4
  servaddr.sin_addr.s_addr=*(uint32_t *)server->h_addr;//地址转换,32位大端字序
  servaddr.sin_port=htons((uint16_t)ECHO_PORT);//设定端口,16位大端字序转换

  if((ret=connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)))<0) //创建连接
  {
      perror("ERROR connecting");
      goto failed;
  }
  pthread_create(&t1, NULL, (void*)rev, NULL);

  while(1)
  {
      printf("Enter the message : ");
      if(fgets(buffer,sizeof(buffer)-1,stdin)==NULL)break;
      if((ret=write(sockfd,buffer,strlen(buffer)+1,))<0)
      {
          perror("ERROR writing to socket");
          break;
      }
  }
  
failed:
  close(sockfd);
  return ret<0?RET_ERR:RET_OK;
}

(3)UDP单播

在发送函数中指定udp包发送的具体地址。
客户端:
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<errno.h>
#include <pthread.h>

#define RET_OK 0
#define RET_ERR -1

#define LISTEN_QUEUE_NUM 5
#define BUFFER_SIZE 256

#define ECHO_PORT 2029

int sockfd;
struct sockaddr_in servaddr,recvaddr;
pthread_t t1;

void rev()
{
  int ret;
  char buffer[BUFFER_SIZE];
  uint32_t len=sizeof(recvaddr);
  while(1)
  {
    if((ret=recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&recvaddr,&len))<0)
    {
        perror("ERROR reading from socket");
        break;
    }
    buffer[ret]=0;
    printf("[form:%s:%u]%s\n",inet_ntoa(recvaddr.sin_addr),ntohs(recvaddr.sin_port), buffer);
  }
}

int main(int argc,char **argv)
{
  int opt=1;
  int ret=RET_OK;
  char buffer[BUFFER_SIZE];
  struct hostent *server;

  if(argc<2)
  {
    fprintf(stderr,"usage &s hostname\n",argv[0]);
    return RET_ERR;
  }
  
  if((server=gethostbyname(argv[1]))==NULL)
  {
    herror("gethostbyname.");
    return RET_ERR;
  }
  
  if((sockfd=socket(AF_INET,SOCK_DGRAM,0))<0)
  {
      herror("ERROR opening socket");
      return RET_ERR;
  }
  memset(&servaddr,0,sizeof(servaddr));//申请信息表空间

  servaddr.sin_family=AF_INET;//设为IPv4
  servaddr.sin_addr.s_addr=*(uint32_t *)server->h_addr;//地址转换,32位大端字序
  servaddr.sin_port=htons((uint16_t)ECHO_PORT);//设定端口,16位大端字序转换

  pthread_create(&t1, NULL, (void*)rev, NULL);

  while(1)
  {
    printf("Enter the message : ");
    if(fgets(buffer,sizeof(buffer)-1,stdin)==NULL)break;
    if((ret=sendto(sockfd,buffer,strlen(buffer)+1,0,(struct sockaddr*)&servaddr,sizeof(servaddr)))<0)
    {
      perror("ERROR writing to socket");
      break;
    }
  }
failed:
  close(sockfd);
  return ret<0?RET_ERR:RET_OK;
}

服务器端同上;
运行过程:./client ip地址;
运行结果:一个服务器终端可以接收数据。

(4)支持多人聊天和私聊的客户端 

跟具字符串格式判断使用发送的是具体地址还是广播地址。

#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<errno.h>
#include<pthread.h>

#define RET_OK 0
#define RET_ERR -1

#define LISTEN_QUEUE_NUM 5
#define BUFFER_SIZE 256

#define ECHO_PORT 2029

struct sockaddr_in recvaddr;
char buffer[BUFFER_SIZE];

void recv2(void* sockfd)
{
 uint32_t len=sizeof(recvaddr);
 int ret;
 while(1)
 { 
  if((ret=recvfrom((int)sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&recvaddr,&len))<0)
  {
   perror("ERROR reading from socket");
   break;
  }
  buffer[ret]=0;
  printf("Server echo message: %s\n",buffer);
 }
}

int main(int argc,char **argv)
{
 int sockfd;
 pthread_t t1;
 int ret=RET_OK;
 struct sockaddr_in servaddr,tempaddr;
 struct hostent *server;
 char tempbuf[BUFFER_SIZE],buf[BUFFER_SIZE];
<span style="white-space:pre">	</span>
 if(argc<2)
 {
   fprintf(stderr,"usage &s hostname\n",argv[0]);
   return RET_ERR;
 }
 if((server=gethostbyname(argv[1]))==NULL)
 {
   herror("gethostbyname.");
   return RET_ERR;
 }
 if((sockfd=socket(AF_INET,SOCK_DGRAM, IPPROTO_UDP))<0)
 {
   herror("ERROR opening socket");
   return RET_ERR;
 }
 memset(&servaddr,0,sizeof(servaddr));//申请信息表空间
<span style="white-space:pre">	</span>
 servaddr.sin_family=AF_INET;//设为IPv4
 servaddr.sin_addr.s_addr=*(uint32_t *)server->h_addr;//地址转换,32位大端字序
 servaddr.sin_port=htons((uint16_t)ECHO_PORT);//设定端口,16位大端字序转换
<span style="white-space:pre">	</span>
 pthread_create(&t1,NULL,(void *)recv2,(void *)sockfd);
 while(1)
 {
   printf("Enter the message : ");
   if(fgets(buffer,sizeof(buffer)-1,stdin)==NULL)break;
<span style="white-space:pre">	</span>
   if(strstr(buffer,":") && strstr(buffer,"@"))  /*私人聊天*/
   {
     tempaddr.sin_family=AF_INET;                
     tempaddr.sin_addr.s_addr=inet_addr(strtok(buffer,":"));//地址
     tempaddr.sin_port=htons(atoi(strtok(NULL,"@")) );
     char *del=":@";
     strcpy(tempbuf,strtok(NULL,del));
     if((ret=sendto(sockfd,tempbuf,strlen(tempbuf),0,(struct sockaddr*)&tempaddr,sizeof(tempaddr)))<0)
     {
 <span style="white-space:pre">	</span>perror("ERROR writing to socket");
 <span style="white-space:pre">	</span>break;
     }           
   }
   else/*多人聊天*/
   {
    if((ret=sendto(sockfd,buffer,strlen(buffer),0,(struct sockaddr*)&servaddr,sizeof(servaddr)))<0)
    {
      perror("ERROR writing to socket");
      break;
    }
   }
 }
<span style="white-space:pre">	</span>  
 close(sockfd);
 return ret<0?RET_ERR:RET_OK;
}

服务器端同上

(5) TCP半双工聊天

服务器每接受一个连接,返回数据后就关闭连接。(这个模式实用性觉得很小)
客户端阻塞发送和接收。
客户端:
 
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<unistd.h>
#include<errno.h>
#include<signal.h>

#define RET_OK 0
#define RET_ERR -1

#define LISTEN_QUEUE_NUM 5
#define BUFFER_SIZE 256

#define ECHO_PORT 2029

int main(int argc,char **argv)
{
  int sockfd,ret=RET_OK;
  struct sockaddr_in servaddr;
  struct hostent *server;

  char buffer[BUFFER_SIZE];

  if(argc<2)
  {
      fprintf(stderr,"usage %s hostname\n",argv[0]);
      return RET_ERR;
  }
  if((server=gethostbyname(argv[1]))==NULL) //由域名获取IP地址
  {
      herror("gethostbyname.");
      return RET_ERR;
  }
  if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0) //创建套接字
  {
      perror("ERROR opening socket");
      return RET_ERR;
  }

  memset(&servaddr,0,sizeof(servaddr));//申请信息表空间

  servaddr.sin_family=AF_INET;//设为IPv4
  servaddr.sin_addr.s_addr=*(uint32_t *)server->h_addr;//地址转换,32位大端字序
  servaddr.sin_port=htons((uint16_t)ECHO_PORT);//设定端口,16位大端字序转换

  if((ret=connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)))<0) //创建连接
  {
      perror("ERROR connecting");
      goto failed;
  }
  while(1)
  {
    printf("\tEnter the message:");
    if(fgets(buffer,sizeof(buffer)-1,stdin)==NULL) //输入字符到buffer指针
    {
			break;
    }
    if((ret=write(sockfd,buffer,strlen(buffer)))<0)   //把buffer的内容 写入strlen(buffer)的字节到sockfd的socket中
    {
      perror("ERROR write to socket");
      break;
    }
    if((ret=read(sockfd,buffer,sizeof(buffer)-1))<0)//读取到buffer中
    {
      perror("ERROR read from socket");
      break;
    }
    else
    {
      buffer[ret]='\0';
      printf("\tServer echo message: %s\n",buffer);
    }
    if(ret==0)
    {
      printf("Server disconnect\n");
      break;
    }
  }
  
failed:
  close(sockfd);
  return ret<0?RET_ERR:RET_OK;
}


服务器端:
 
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<unistd.h>
#include<errno.h>
#include<signal.h>

#define RET_OK 0
#define RET_ERR -1

#define LISTEN_QUEUE_NUM 5
#define BUFFER_SIZE 256
#define ECHO_PORT 2029
char buffer[BUFFER_SIZE];
从socket读一行数据到缓冲区
static int my_readline(int fd,void *buf,unsigned len)
{
  int n,rc;
  char c,*ptr;
  ptr=buf;
  for(n=1;n<len;n++)
  {
	again:
    if((rc=read(fd,&c,1))==1)
    {
      *ptr++=c;
      if(c=='\n')break;
    }
    else if(rc==0)
    {
      if(n==1)return 0;
      else break;
    }else
    {
      if(errno==EINTR)goto again;
      return -1;
    }
  }
  *ptr=0;
  return n;
}

static int proc_echo(int sockfd)
{
  int ret;
  while(1)
  {
    if((ret=my_readline(sockfd,buffer,sizeof(buffer)-1))<0)
    {
        perror("read");
        return -1;
    }else if(ret==0)
    {
        printf("client disconnect.\n");
        return 0;
    }
    buffer[ret]='\0';
    printf("client echo message:  %s\n",buffer);
    printf("\tEnter message:\n ");
    fflush(stdout);
    if(fgets(buffer,sizeof(buffer),stdin)==NULL) //输入字符到buffer指针
    {
			break;
    }
    if((ret=write(sockfd,buffer,strlen(buffer)))<0)    //把buffer的内容 写入strlen(buffer)的字节到nsock的socket中
    {
      perror("ERROR write to socket");
      break;
    }
  }
    
  return -1;
}

int main(int argc,char **argv)
{
  int sockfd,nsock,ret=0;
  uint32_t len;
  struct sockaddr_in servaddr,cliaddr;
  if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0) 
  {
    perror("ERROR opening socket");
    return RET_ERR;
  }
  memset(&servaddr,0,sizeof(servaddr));

  servaddr.sin_family=AF_INET;
  servaddr.sin_addr.s_addr=INADDR_ANY;
  servaddr.sin_port=htons(ECHO_PORT);

  if((ret=bind(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr)))<0)
  {
    perror("ERROR on binding");
    goto failed;
  }

  if((ret=listen(sockfd,LISTEN_QUEUE_NUM))!=0)
  { 
   perror("ERROR on listen");
    goto failed;
  }    
  printf("\tserver is listening>>>\n");
  while(1)
  {
    len=sizeof(cliaddr);
    if((nsock=accept(sockfd,(struct sockaddr*)&cliaddr,(uint32_t*)&len))<0)
    {
      perror("accept");    
      break;        
    }
    printf("\tclient connect success!!!\n");
    proc_echo(nsock);
    close(nsock);
  }
  
failed:
  close(sockfd);
  return ret<0?RET_ERR:RET_OK;
}

运行:
./ser, server is listening>>>
./cli 服务器地址;
Enter:  ......
服务器:读出数据,并从键盘接收数据回发给客户;(此时客户端阻塞直到收到服务器的回信).

(6)TCP全双工通信

服务器每接受一个连接后,创建线程来处理数据收发业务。
客户端的读写业务分别在两个不同的线程内。

服务器与客户端可以异步发送信息

客户端:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<unistd.h>
#include<errno.h>
#include<signal.h>
#include"echo_net.h"

#define RET_OK 0
#define RET_ERR -1

#define LISTEN_QUEUE_NUM 5
#define BUFFER_SIZE 256
#define ECHO_PORT 2029

static void proc_echo(void *sockfd)
{
  int ret;
  char buffer[BUFFER_SIZE];  
  while(1)
  {
    if((ret=read((int)sockfd,buffer,sizeof(buffer)-1))<0)//读取到buffer中
    {
      perror("ERROR read from socket");
      break;
    }
    if(ret==0)
    {
      printf("Server disconnect\n");
      //pthread_cancel(t2);
      break;
    }

    buffer[ret]='\0';
    printf("\tServer echo message: %s\n",buffer);
  }
}

int main(int argc,char **argv)
{
  int sockfd,ret=RET_OK;
  pthread_t t2;
  struct sockaddr_in servaddr;
  struct hostent *server;
  char buffer[BUFFER_SIZE] ;

  if(argc<2)
  {
    fprintf(stderr,"usage %s hostname\n",argv[0]);
    return RET_ERR;
  }
  if((server=gethostbyname(argv[1]))==NULL) //由域名获取IP地址
  {
    herror("gethostbyname.");
    return RET_ERR;
  }
  if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0) //创建套接字
  {
    perror("ERROR opening socket");
    return RET_ERR;
  }

  memset(&servaddr,0,sizeof(servaddr));//申请信息表空间

  servaddr.sin_family=AF_INET;//设为IPv4
  servaddr.sin_addr.s_addr=*(uint32_t *)server->h_addr;//地址转换,32位大端字序
  servaddr.sin_port=htons((uint16_t)ECHO_PORT);//设定端口,16位大端字序转换

  if((ret=connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)))<0) //创建连接
  {
    perror("ERROR connecting");
    goto failed;
  }
  pthread_create(&t2,NULL,(void *)proc_echo,(void*)sockfd);
  while(1)
  {
    printf("\tEnter the message:\n");
    if(fgets(buffer,sizeof(buffer)-1,stdin)==NULL)//输入字符到buffer指针
    {
        break;
    }
    if((ret=write(sockfd,buffer,strlen(buffer)))<0)//把buffer的内容 写入strlen(buffer)的字节到sockfd的socket中
    {
        perror("ERROR write to socket");
        break;
    }
  }

failed:
  close(sockfd);
  return ret<0?RET_ERR:RET_OK;
}

服务器端:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<unistd.h>
#include<errno.h>
#include<signal.h>
#include <pthread.h>
#include"echo_net.h"

#define BUFFER_SIZE 256

static int my_readline(int fd,void *buf,unsigned len)
{
  int n,rc;
  char c,*ptr;
  ptr=buf;
  for(n=1;n<len;n++)
  {
    again:
    if((rc=read(fd,&c,1))==1)
    {
      *ptr++=c;
      if(c=='\n')break;
    }else if(rc==0)
    {
      if(n==1)return 0;
      else break;
    }else
    {
      if(errno==EINTR)goto again;
      return -1;
    }
  }
  *ptr=0;
  return n;
}

static void proc_echo_2(void *sockfd)
{
    int ret;
    char buffer[BUFFER_SIZE];	
    char readBuf[BUFFER_SIZE];
    while(1)
    {
	//消息接收
        if((ret=my_readline(sockfd,readBuf,sizeof(buf)-1))<0)
        {
            perror("read");
            return -1;
        }else if(ret==0)
        {
            printf("client disconnect.\n");
            pthread_cancel(t2);    
            return 0;
        }
        buf[ret]='\0';
        printf("client echo message:  %s",readBuf);
        fflush(stdout); 
	//消息发送
        printf("\tEnter message:\n ");
        if(fgets(buffer,sizeof(buffer),stdin)==NULL) //输入字符到buffer指针
        {
	   printf("没有输入字符");
           continue;
        }
        if((ret=write((int)sockfd,buffer,strlen(buffer)))<0)    //把buffer的内容 写入strlen(buffer)的字节到nsock的socket中
        {
             perror("ERROR write to socket");
             break;
        }
    }
    close(nsock);
}

int main(int argc,char **argv)
{
    int sockfd,nsock,ret=0;
    int opt;pthread_t t2;
    char buf[32];
    uint32_t len;
    
    struct sockaddr_in servaddr,cliaddr;
    if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)
    {
        perror("ERROR opening socket");
        return RET_ERR;
    }
    //SO_REUSEADDR允许重用本地址
    if((ret=setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)))<0)
    {
      perror("ERROR setsockopt");
      return RET_ERR;
    }

    memset(&servaddr,0,sizeof(servaddr));

    servaddr.sin_family=AF_INET;
    servaddr.sin_addr.s_addr=INADDR_ANY;
    servaddr.sin_port=htons(ECHO_PORT);

    if((ret=bind(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr)))<0)
    {
      perror("ERROR on binding");
      goto failed;
    }

    if((ret=listen(sockfd,LISTEN_QUEUE_NUM))!=0)
    {
	perror("ERROR on listen");
	goto failed;
   }
		
    printf("\tserver is listening>>>\n");
    while(1)
    {
        len=sizeof(cliaddr);
        if((nsock=accept(sockfd,(struct sockaddr*)&cliaddr,(uint32_t*)&len))<0)
        {
              perror("accept");
              break;
        }
        printf("\tclient connect success!!!\n");
    		//printf("\tclient id =%s\n",inet_ntoa(cliaddr.sin_addr));
        printf("client id =%s\n",
                inet_ntop(cliaddr.sin_family,&cliaddr.sin_addr,buf,sizeof(buf)));
        printf("\tport =%d\n",ntohs(cliaddr.sin_port));
        pthread_create(&t2,NULL,(void *)proc_echo_2,(void*)sockfd);
        proc_echo(nsock);
    }

failed:
	close(sockfd);
	return ret<0?RET_ERR:RET_OK;
}


(7) TCP I/O复用之select

复用io,非阻塞监听事件。
服务器非阻塞监听socket读事件,处理并返回给客户端。
客户端非阻塞监听socket读事件和标准输入事件,并处理。

客户端:
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<errno.h>

#define RET_OK 0
#define RET_ERR -1

#define LISTEN_QUEUE_NUM 5
#define BUFFER_SIZE 256

#define ECHO_PORT 2029

int main(int argc,char **argv)
{
  int maxfd=0;
  fd_set rset,set;
  int nfound,bytesread;
  int sockfd;
  int ret=RET_OK;
  struct sockaddr_in servaddr;
  struct hostent *server;
  char buffer[BUFFER_SIZE];

  if(argc<2)
  {
      fprintf(stderr,"usage &s hostname\n",argv[0]);
      return RET_ERR;
  }
  
  if((server=gethostbyname(argv[1]))==NULL)
  {
      herror("gethostbyname.");
      return RET_ERR;
  }
  
  if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)
  {
      herror("ERROR opening socket");
      return RET_ERR;
  }

  memset(&servaddr,0,sizeof(servaddr));//申请信息表空间

  servaddr.sin_family=AF_INET;//设为IPv4
  servaddr.sin_addr.s_addr=*(uint32_t *)server->h_addr;//地址转换,32位大端字序
  servaddr.sin_port=htons((uint16_t)ECHO_PORT);//设定端口,16位大端字序转换

  if(connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
  {
      perror("ERROR connect");
      return RET_ERR;
  }

  maxfd=fileno(stdin);//获取标准读的文件描述符
  FD_ZERO(&set);
  FD_SET(sockfd,&set);//把连接socket加入文件描述符集
  FD_SET(maxfd,&set);//把标准输入输出描述符加入文件描述符集
  maxfd=(maxfd>sockfd?maxfd:sockfd)+1;//监听数是最大的文件描述符+1

  while(1)
  {
    rset=set;
    if((nfound=select(maxfd,&rset,(fd_set*)0,(fd_set*)0,NULL))<0)
    {
        if(errno==EINTR)
        {
            fprintf(stderr,"interrupted system call\n");
            continue;
        }
        perror("select");
        exit(1);
    }
    
    if(FD_ISSET(fileno(stdin),&rset))//如果是标准输入事件,则输入字符到缓冲区,发送缓冲区数据
    {
      if(fgets(buffer,sizeof(buffer)-1,stdin)==NULL)
      {
        if(ferror(stdin))
        {
            perror("stdin");
            return -1;
        }
        return 0;
      }
      if(write(sockfd,buffer,strlen(buffer))<0)
      {    
        perror("ERROR writing");
        return -1;
      }
    }
    if(FD_ISSET(sockfd,&rset))//如果是socket读事件(这里socket只有读事件,监听的是读文件描述符集),则读取socket缓冲区的数据到缓存
    {
      if((bytesread=read(sockfd,buffer,sizeof(buffer)))<0)
      {    
        perror("ERROR read");
        exit(1);
      }else if(bytesread==0)//如果有读事件而读缓冲区是0字节,表示对端已关闭socket
      {
        fprintf(stderr,"server disconnect\n");
        exit(0);
      }
            
      buffer[bytesread]=0;//最后加上结束符
      printf("buffer: %s",buffer);//输出读取socket的数据
    }
  }
  return 0;
}



服务器端:
#include<sys/types.h>
#include<ctype.h>
#include<strings.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<arpa/inet.h>
#include<errno.h>
#include<sys/time.h>
#include<stdio.h>
#include<string.h>
#include<sys/select.h>
#include<stdlib.h>
#include<signal.h>
#include<sys/wait.h>

#define RET_OK 0
#define RET_ERR -1
#define LISTEN_QUEUE_NUM 5
#define BUFFER_SIZE 256
#define ECHO_PORT 2029

int main(int argc,char **argv)
{
  struct sockaddr_in servaddr,remote;
  int request_sock,new_sock;
  int nfound,fd,maxfd,bytesread;
  uint32_t addrlen;
  fd_set rset,set;
  struct timeval timeout;
  char buf[BUFFER_SIZE];

  if((request_sock=socket(AF_INET,SOCK_STREAM, IPPROTO_TCP))<0)
  {
      perror("ERROR opening socket");
      return RET_ERR;
  }
  memset(&servaddr,0,sizeof(servaddr));

  servaddr.sin_family=AF_INET;
  servaddr.sin_addr.s_addr=INADDR_ANY;
  servaddr.sin_port=htons((uint16_t)ECHO_PORT);

  if(bind(request_sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)//绑定socket到地址(设置socket监听方式)
  {
      perror("ERROR on binding");
      return RET_ERR;
  }
  if(listen(request_sock,LISTEN_QUEUE_NUM)<0)//开始监听socket
  {
      perror("ERROR on listen");
      return RET_ERR;
  }

  FD_ZERO(&set);
  FD_SET(request_sock,&set);
  maxfd=request_sock;
  while(1)
  {
    rset=set;
    timeout.tv_sec=0;
    timeout.tv_usec=50000000;.//最大50ms监听超时时间
    if((nfound=select(maxfd+1,&rset,(fd_set*)0,(fd_set*)0,&timeout))<0)//非阻塞监听读事件(设置了最大监听事件描述符)
    {
      perror("select");
      return -1;
    }
    else if(nfound==0)//没有触发事件则继续监听
    { 
      fflush(stdout);
      continue;
    }

    if(FD_ISSET(request_sock,&rset))
    {
      addrlen=sizeof(remote);
      if((new_sock=accept(request_sock,(struct sockaddr*)&remote,&addrlen))<0)
      {
          perror("accept");
          return -1;
      }
      printf("connection from host %s,port %d,socket %d\r\n",inet_ntoa(remote.sin_addr),ntohs(remote.sin_port),new_sock);
      FD_SET(new_sock,&set);//接收新的连接后,把连接加入需要监听的文件描述符集
      if(new_sock > maxfd)maxfd=new_sock;
      FD_CLR(request_sock,&rset);
      nfound--;
    }

    for(fd=0;fd<=maxfd&&nfound>0;fd++)
    {
      if(FD_ISSET(fd,&rset))
      {
        nfound--;
        if((bytesread=read(fd,buf,sizeof(buf)-1))<0)
        {
            perror("read");
        }
        if(bytesread==0)//对端关闭了
        {
            fprintf(stderr,"server: end of file on %d\r\n",fd);
            FD_CLR(fd,&set);
            close(fd);
            continue;
        }
        buf[bytesread]=0;
        printf("%s: %d bytes from %d: %s\n",argv[0],bytesread,fd,buf);
        
        if(write(fd,buf,bytesread)<0)//echo 业务,把读到的数据返回给客户端
        {
            perror("echo");//写入有误
            FD_CLR(fd,&set);//减少需要监听的socket
            close(fd);
        }
      }
    }
  }
  return 0;
}



(8)TCP I/O复用之poll

使用poll监听的复用io
服务器非阻塞监听复用io事件,处理socket事件
客户端阻塞发送和接收数据

poll与select的区别:
1)遍历的文件描述符的范围不一样(前者是poll数组,后者是文件描述符集,文件描述符集是以最大的文件描述符为最大数组成员)
2)需要监听的文件描述符可以注册各自需要的事件

客户端:
 
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/time.h>
#include<netinet/in.h>
#include<errno.h>
#include<ctype.h>
#include<netdb.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/select.h>
#include<sys/poll.h>

#define RET_OK 0
#define RET_ERR -1
#define LISTEN_QUEUE_NUM 5

#define BUFFER_SIZE 256
#define ECHO_PORT   2029

int main(int argc, char **argv)
{
  int sock;
  struct sockaddr_in servaddr;
  struct hostent *server;
  static struct pollfd cpoll[2];
  int nfound, bytesread;
  char buf[BUFFER_SIZE];

  if(argc < 2)
  {
      fprintf(stderr, "usage %s hostname\n", argv[0]);
      return RET_ERR;
  }
  if((server = gethostbyname(argv[1])) == NULL)
  {
      perror("gethostbyname. ");
      return RET_ERR;
  }

  /*setup socket*/
  if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)//tcp socket
  {
      perror("socket");
      return -1;
  }

  /*fillup ip address structure*/
  memset(&servaddr, 0, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = *(uint32_t *) server->h_addr;
  servaddr.sin_port = htons((uint16_t) ECHO_PORT);

  /*connect to server*/
  if(connect(sock, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) //连接到对端
  {
      perror("connect");
      return -1;
  }
  //加入需要监听的文件描述符以及相关的事件
  /*fill up poll struct*/
  cpoll[0].fd = fileno(stdin);//加入监听描述符
  cpoll[0].events = POLLIN;//加入监听事件
  cpoll[1].fd = sock;//加入监听描述符
  cpoll[1].events = POLLIN;//加入监听事件

  while (1)
  {
    if ((nfound = poll(cpoll, 2, -1)) < 0)//阻塞监听poll数组事件(socket事件),(在没有别的业务的情况下可以阻塞接收)
    {
      if (errno == EINTR)//是中断事件的错误,则忽略
      {
        fprintf(stderr, "interruptedb system call\n");
        continue;
      }
      perror("select");
      exit(1);
    }
    
    if (cpoll[0].revents & (POLLIN | POLLERR))//检查输入设备的读事件
    {
      if(fgets(buf, sizeof(buf), stdin) == NULL) //从标准输入设备描述符的缓冲区读取数据到用户缓冲区
      {
        if (ferror(stdin))
        {
            perror("stdin");
            return -1;
        }
        return 0;
      }
      /*write to socket*/
      if (write(sock, buf, strlen(buf)) < 0)
      {
          perror("write");
          return -1;
      }
    }

    if(cpoll[1].revents & (POLLIN | POLLERR))//检查socket的读事件
    {
      if((bytesread = read(sock, buf, sizeof(buf))) < 0)//从socket的缓冲区读取数据到用户缓冲区
      {
        perror("read");
        exit(1);
      }
      else if(bytesread == 0)//对端关闭,则关闭socket
      {
        fprintf(stderr, "server disconnect\n");
        exit(0);
      }
      buf[bytesread] = 0;
      printf("%s\n", buf);
    }
  }
  
  return 0;
}




服务器端:
#include<sys/types.h>
#include<ctype.h>
#include<strings.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<errno.h>
#include<sys/time.h>
#include<stdio.h>
#include<string.h>
#include<sys/poll.h>
#include<stdlib.h>

#define LISTEN_QUEUE_NUM 5
#define MAX_CONNECT 1024

#define BUFFER_SIZE 256
#define ECHO_PORT 2029

static struct pollfd clipoll[MAX_CONNECT];
int max = 0;//poll数组中的最大的当前使用的文件描述符

static void poll_init()//初始化poll结构数组
{
  int i;
  for (i = 0; i < MAX_CONNECT; i++)
   clipoll[i].fd = -1;
}

static int poll_alloc()//获取一个没用的poll结构数组空槽的下标,找不到则返回-1
{
    int i;
    for (i = 0; i < MAX_CONNECT; i++)
    {
        if (clipoll[i].fd < 0)
        return i;
    }
    return -1;
}


static void update_max()//获取在使用的文件描述符在poll数组中的下标中的最大的一个
{
    int i;
    max = 0;
    for (i = 0; i < MAX_CONNECT; i++)
    {
        if (clipoll[i].fd > 0)
        max = i;
    }
}

static int poll_free(int i)//释放pollfd结构数组的一个成员
{
    close(clipoll[i].fd);
    clipoll[i].fd = -1;
    clipoll[i].events = 0;
    clipoll[i].revents = 0;
    update_max();
}

static int poll_set_item(int fd, uint32_t events)//加入一个需要监听的文件描述符(和相关事件)到poll数组的一个空槽中
{
  int i;
  if ((i = poll_alloc()) < 0)//获取一个pollfd结构,加入文件描述符,和对应的事件
    return -1;
  clipoll[i].fd = fd;
  clipoll[i].events = events;
  clipoll[i].revents = 0;//清空读事件
  if (i > max)
   max = i;
  return 0;
}

int main(int argc, char **argv)
{
  struct sockaddr_in servaddr, remote;
  int request_sock, new_sock;
  int nfound, i, bytesread;
  uint32_t addrlen;
  char buf[BUFFER_SIZE];
  /*setup socket*/
  if ((request_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
  {
      perror("socket");
      return -1;
  }
  /*fill up ip address structure*/
  memset(&servaddr, 0, sizeof(servaddr));
  servaddr.sin_family = AF_INET;//协议簇
  servaddr.sin_addr.s_addr = INADDR_ANY;//监听所有地址
  servaddr.sin_port = htons(ECHO_PORT);//监听端口
  if (bind(request_sock, (struct sockaddr *) &servaddr, sizeof(servaddr)) <  0)//设置socket监听方式(绑定socket到地址)
  {
    perror("bind");
    return -1;
  }
  if (listen(request_sock, LISTEN_QUEUE_NUM) < 0)//开始socket监听事件
  {
    perror("listen");
    return -1;
  }
  poll_init(); //初始化poll数组
  poll_set_item(request_sock, POLLIN);//把需要监听的socket(和其读事件)加入到poll数组
  while (1)
  {
    if ((nfound = poll(clipoll, max + 1, 50)) < 0)//非阻塞监听poll文件描述符的事件(max是poll数组中的在使用的文件描述符最大的下标),超时时间为50ms
    {
      if (errno == EINTR)//忽略中断的错误
      {
        printf("interruptedb system call\n");
        continue;
      }
      perror("poll");
      return -1;
    }
    else if(nfound == 0)//没有监听到发生事件的文件描述符
    {
      fflush(stdout);
      continue;
    }
    
    if (clipoll[0].revents & (POLLIN | POLLERR))//第一个是监听socket,若其发送了读事件,则接受新连接
    {
      addrlen = sizeof(remote);
      if ((new_sock = accept(request_sock, (struct sockaddr *) &remote,    &addrlen)) < 0)
      {
          perror("accept");
          return -1;
      }
      printf("connection fromm host %s,port %d, socket %d\r\n",
              inet_ntoa(remote.sin_addr), ntohs(remote.sin_port), new_sock);
      if (poll_set_item(new_sock, POLLIN) < 0)//加入新的连接到poll数组
          fprintf(stderr, "Too many connects\r\n");
      nfound--;
    }
    for (i = 1; i <= max && nfound > 0; i++)//遍历检查通信socket是否有触发事件
    {
      if (clipoll[i].fd >= 0 && clipoll[i].revents & (POLLIN | POLLERR))//poll数组的socket文件描述符有读事件
      {
        nfound--;
        if((bytesread = read(clipoll[i].fd, buf, sizeof(buf) - 1)) < 0)//读取socket 的数据到用户缓冲区
        {
            perror("read");
            poll_free(i);
            continue;
        }
        if(bytesread == 0)//对端关闭了
        {
            fprintf(stderr, "server: end of file on %d\r\n", clipoll[i].fd);
            poll_free(i);
            continue;
        }
        buf[bytesread] = 0;
        printf("%s:%d bytes from %d :%s\n", argv[0], bytesread,
        clipoll[i].fd, buf);
        if (write(clipoll[i].fd, buf, bytesread) != bytesread)//echo业务,返回读取的数据到对端
        {
            perror("echo");
            poll_free(i);//写错误(表示对端关闭了)则关闭socket
            continue;
        }
      }
    }
  }
  
  return 0;
}






  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值