Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3514561
  • 博文数量: 1805
  • 博客积分: 135
  • 博客等级: 入伍新兵
  • 技术积分: 3345
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-19 20:01
文章分类

全部博文(1805)

文章存档

2017年(19)

2016年(80)

2015年(341)

2014年(438)

2013年(349)

2012年(332)

2011年(248)

分类: C/C++

2015-09-29 01:29:04

原文地址:TCP/UDP编程模型和实例 作者:逼良为娼

实例是本人初学linux时弄的,写得有点羞涩,但最近太忙了,也懒得改了,大家就将就着看吧。
首先了解一下下面的概念:
    循环服务器:循环服务器在同一个时刻只可以响应一个客户端的请求
    并发服务器:并发服务器在同一个时刻可以响应多个客户端的请求
 
1.循环服务器:UDP服务器
UDP循环服务器的实现非常简单:UDP服务器每次从套接字上读取一个客户端的请求,处理, 然后将结果返回给客户机.
可以用下面的算法来实现.
   socket(...);
   bind(...);
   while(1)
    {
         recvfrom(...);
         process(...);
         sendto(...);
   }
因为UDP是非面向连接的,没有一个客户端可以老是占住服务端. 只要处理过程不是死循环, 服务器对于每一个客户机的请求总是能够满足.
实例:
#include
#include
#include
#include
#include
#include
#include
#define PORT 1234
#define MAXDATASIZE 100
int main(int argc, char *argv[])
{
 int sockfd;
 int num;
 struct sockaddr_in server,client;
 int sin_size;
 char msg[MAXDATASIZE+1];
 if((sockfd = socket(AF_INET,SOCK_DGRAM,0)) == -1) //创建套接字
 {
  printf("create socket error.\n");
  exit(1);
 }
 bzero(&server,sizeof(server)); //清空,以便存贮数据
 server.sin_family = AF_INET;
 server.sin_port = htons(PORT);
 server.sin_addr.s_addr = htonl(INADDR_ANY);
 if(bind(sockfd,(struct sockaddr*)&server,sizeof(struct sockaddr)) == -1)  //server地址与socket绑定
 {
  printf("bind error.\n");
  exit(1);
 }
 sin_size = sizeof(struct sockaddr_in);
 while(1){  //服务器一直运行
  num = recvfrom(sockfd,msg,MAXDATASIZE,0,(struct sockaddr *)&client,&sin_size);//函数原型ssize_t recvfrom(int s,void *buf,size_t len,int flags,struct sockaddr *from,socklen_t *fromlen);由于是无连接的,所以函数中有客户机地址参数  //意思是接受sockfd中的数据到msg中,最大只能接受MAXDATASIZE到msg(msg大小只有MAXDATASIZE),此套接字sockfd连接的是client地址
  if(num < 0)
  {
   printf("recvfrom error.\n");
   exit(1);
  }
  msg[num] = '\0'; //在接受的数据结尾加上'\0'表示字符串结束
  printf("You got a message (%s) from %s\n",msg,inet_ntoa(client.sin_addr));
  sendto(sockfd,"Welcome to my server.\n",22,0,(struct sockaddr *)&client,sin_size);
  if(!strcmp(msg,"quit"))  //int strcmp(const char *s1,const char *s2),返回值:当s1和s2相同时返回0,不相同时返回相匹配的字符个数
  {
   break;
  }
 }
 
 close(sockfd);
 
}
 
2.循环服务器:TCP服务器
TCP循环服务器的实现也不难:TCP服务器接受一个客户端的连接,然后处理,完成了这个客户的所有请求后,断开连接.
算法如下:
        socket(...);
        bind(...);
        listen(...);
        while(1)
        {
                accept(...);
                while(1)
                {
                        read(...);
                        process(...);
                        write(...);
                }
                close(...);
        }
TCP循环服务器一次只能处理一个客户端的请求.只有在这个客户的所有请求都满足后, 服务器才可以继续后面的请求.这样如果有一个客户端占住服务器不放时,其它的客户机都不能工作了.因此,TCP服务器一般很少用循环服务器模型的.
实例:
//TCP协议模板,服务器端
#include
#include
#include
#include
#include
#include
#include
#define PORT 1234
#define BACKLOG 1
int main(void)
{
 int listenfd,connectfd;
 struct sockaddr_in server;
 struct sockaddr_in client;
 int sin_size;
 if((listenfd = socket(AF_INET,SOCK_STREAM,0)) == -1)//创建socket套接字
 {
  perror("Creating socket failed.");
  exit(1);
 }
 int opt = SO_REUSEADDR;
 setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//设置listenfd所指的socket状态。SOL_SOCKET为通用套接字选项,设置它以存取socket层,SO_REUSEADDR允许在bind()过程中本地地址可重复使用
 bzero(&server,sizeof(server));//清零
 server.sin_family = AF_INET;
 server.sin_port = htons(PORT);
 server.sin_addr.s_addr = htonl(INADDR_ANY);
//对地址结构体各成员赋值
 if(bind(listenfd,(struct sockaddr *)&server,sizeof(struct sockaddr))) //本地地址与套接字绑定
 {
  perror("bind error");
  exit(1);
 }
 if(listen(listenfd,BACKLOG) == -1)//套接字listenfd进行监听
 {
  perror("listen error.");
  exit(1);
 }
 sin_size = sizeof(struct sockaddr_in);
 if((connectfd = accept(listenfd,(struct sockaddr *)&client,&sin_size)) == -1)//与客户端建立连接
 {
  perror("accept error.");
  exit(1);
 }
 printf("You got a connect from %s\n",inet_ntoa(client.sin_addr));
 send(connectfd,"welcome to my server\n",22,0);//函数原型ssize_t send(int s,const void *buf,size_t len,int flags),用于有连接的socket,当flags为0时send等价于write函数,即把buf中的数据写入套接字文件s中
 close(connectfd);
 close(listenfd);
}
3.并发服务器:TCP服务器
为了弥补循环TCP服务器的缺陷,人们又想出了并发服务器的模型. 并发服务器的思想是每一个客户机的请求并不由服务器直接处理,而是服务器创建一个 子进程来处理.
算法如下:
socket(...);
bind(...);
listen(...);
while(1)
{
        accept(...);
        if(fork(..)==0)
          {
              while(1)
               {        
                read(...);
                process(...);
                write(...);
               }
           close(...);
           exit(...);
          }
        close(...);
}     
TCP并发服务器可以解决TCP循环服务器客户机独占服务器的情况. 不过也同时带来了一个不小的问题.为了响应客户机的请求,服务器要创建子进程来处理. 而创建子进程是一种非常消耗资源的操作.
实例:
//fork()多个进程处理多个客户机的连接
#include
#include
#include
#include
#include
#include
#include
#define PORT 1234
#define BACKLOG 2
#define MAXDATASIZE 1000
void process_cli(int connectfd,struct sockaddr_in client);
int main(void)
{
 int listenfd,connectfd;
 pid_t pid;
 struct sockaddr_in server,client;
 int sin_size;
 if((listenfd = socket(AF_INET,SOCK_STREAM,0)) == -1) //创建socket
 {
  printf("create socket error.\n");
  exit(1);
 }
 int opt = SO_REUSEADDR;
 setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
 //设置socket状态,函数原型:int setsockopt(int s,int level,int optname,const void *optval,socklen_t optlen)
 //setsockopt()用来设置参数s所指定的socket状态。参数level代表欲设置的网络层,一般设为SOL_SOCKET以存取socket层,
 //它有三种值:1、SOL_SOCKET:通用套接字选项;2、IPPROTO_IP:IP选项;3、IPPROTO_TCP:TCP选项
 //参数optname代表获得或设置套接字选项;optval代表欲设置的值;参数optlen为optval的长度
 bzero(&server,sizeof(server)); //置0
 server.sin_family = AF_INET;   //设置地址簇为internet地址簇
 server.sin_port = htons(PORT); //设置端口号并(把主机字节顺序)转化为网络字节顺序
 server.sin_addr.s_addr = htonl(INADDR_ANY); //设置IP地址
 if(bind(listenfd,(struct sockaddr *)&server,sizeof(struct sockaddr)) == -1) //把创建的socket与服务器绑定
 {
  printf("bind error.\n");
  exit(1);
 }
 if(listen(listenfd,BACKLOG) == -1)  //socket进入监听,BACKLOG为请求队列的最大允许数
 {
  printf("listen error.\n");
  exit(1);
 }
 sin_size = sizeof(struct sockaddr_in);
 while(1)
 {
  if((connectfd = accept(listenfd,(struct sockaddr *)&client,&sin_size)) == -1)  //与客户机建立连接
  {
   printf("accept error.\n");
   exit(1);
  }
  pid = fork(); //创建子进程,用来处理与客户机的通信
  if(pid > 0) //在父进程中
  {
   close(connectfd);
   continue;
  }
  else if(pid == 0) //在子进程中
  {
   close(listenfd);
   process_cli(connectfd,client); //与客户机通信
   exit(0);
  }
  else
  {
   printf("fork error.\n");
   exit(0);
  }
 }
 close(listenfd);
}
void process_cli(int connectfd,struct sockaddr_in client)
{
 int num;
 char recvbuf[MAXDATASIZE+1],sendbuf[MAXDATASIZE+1],cli_name[MAXDATASIZE+1];
 printf("You got a connection from %s.\n",inet_ntoa(client.sin_addr));
 num = recv(connectfd,cli_name,MAXDATASIZE,0); //接受客户机传来的客户机名
 //因为客户机是用fgets()发送,此函数会自动在最后一个字符后加上'\0',
 if(num == 0)
 {
  close(connectfd);
  printf("client disconnectde.\n");
  return;
 }
 printf("client,s name is %s\n",cli_name);
 while(num = recv(connectfd,recvbuf,MAXDATASIZE,0)) //接受客户机传来的数据
 {
  recvbuf[num] = '\0'; // 在末尾加'\0'是防止recvbuf中原本就存在数据,这样就不会把多余的数据打印出来
  printf("recerved client(%s) message:\t%s\n",cli_name,recvbuf);
  int i;
  for(i = 0;i < num - 1;i++)
  {
   sendbuf[i] = recvbuf[num - i - 2]; //倒置
  }
  //sendbuf[num - 1] = '\0'; //可不用这条语句,存入内存系统会自动加'\0',传送给客户机时会取'\0'再传送
  //由于'\0'不传送,所以客户机的recv()要在末尾加'\0'再打印出去,以免打印出多余数据
  send(connectfd,sendbuf,strlen(sendbuf),0);
 }
 close(connectfd);
}
 
当然也可以用多线程,因为多进程非常耗资源,创建的子进程太多会使系统崩毁。
多线程实例:
//多线程处理,服务器端
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 1234
#define BACKLOG 5
#define MAXDATASIZE 1000
void process_cli(int connectfd,struct sockaddr_in client);
void *start_routine(void *arg);
struct ARG
{
 int connfd;
 struct sockaddr_in client;
};
int main(void)
{
 int listenfd,connectfd;
 pthread_t thread;
 struct ARG *arg;
 struct sockaddr_in server,client;
 int sin_size;
 if((listenfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
 {
  printf("create socket error.\n");
  exit(1); 
 }
 int opt = SO_REUSEADDR;
 setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
 //设置socket状态
 bzero(&server,sizeof(server)); //置0,用于存放服务器地址
 server.sin_family = AF_INET; //设置server地址中的协议簇,为internet
 server.sin_port = htons(PORT); //设置server地址中的端口号,并转化为网络字符字节
 server.sin_addr.s_addr = htonl(INADDR_ANY); //设置IP为自动获取,INZDDR_ANY可改为确定的IP地址
 if(bind(listenfd,(struct sockaddr *)&server,sizeof(struct sockaddr)) == -1)
 //socket与服务器绑定 
 {
  printf("bind error.\n");
  exit(1);
 }
 if(listen(listenfd,BACKLOG) == -1) //socket监听
 {
  printf("listen error.\n");
  exit(1);
 }
 sin_size = sizeof(struct sockaddr_in);
 while(1)  //不断进行监听与连接
 {
  if((connectfd = accept(listenfd,(struct sockaddr *)&client,&sin_size)) == -1)
  //与客户机连接
  {
   printf("accept error.\n");
   exit(1);
  }
  arg = malloc(sizeof(struct ARG));//分配内存,函数原型:void *malloc(size_t size)
  arg->connfd = connectfd;
  memcpy((void *)&arg->client,&client,sizeof(client));
  //拷贝内存空间,函数原型:void *memcpy(void *dest,const void *src,size_t n)
  //表示从src所指的内存空间区域拷贝n个字节到dest所指内存区域
  if(pthread_create(&thread,NULL,start_routine,(void *)arg))
  //创建线程,函数原型:int pthread_create(pthread_t *thread,pthread_attr_t *attr,void *(*start_routine)(void *),(void *)arg);
  //thread为线程标识符;attr为线程属性设置(NULL为采用默认值);start_routine为线程函数的启示地址,arg为传递给start_routine()的参数
  //这语句的意思是从函数start_routine地址处创建线程,也就是把函数start_routine()作为线程来执行
  {
   printf("pthread create error.\n");
   exit(1);
  }
 }
 close(listenfd);
}
void process_cli(int connectfd,struct sockaddr_in client) //自定义函数用于处理与客户机的通信
{
 int num;
 char recvbuf[MAXDATASIZE+1],sendbuf[MAXDATASIZE+1],cli_name[MAXDATASIZE+1];
 printf("you got a connection from %s\n",inet_ntoa(client.sin_addr));
 num = recv(connectfd,cli_name,MAXDATASIZE,0);
 if(num == 0)
 {
  close(connectfd);
  printf("client disconnected.\n");
  return;
 }
 //cli_name[num - 1] = '\0'; //这句的作用是把从客户机传来的数据的最后一个字符(即回车符)去掉
        //因为客户端是用fgets()接收并传数据,fgets()会接收回车符,这样输出的
        //的数据中在就不会在客户机的名字后换行(即106那行语句的输出)
 printf("client name is %s\n",cli_name);
 while(num = recv(connectfd,recvbuf,MAXDATASIZE,0)) //接收到数据时
 {
  recvbuf[num] = '\0';
  printf("received client (%s) message:%s\n",cli_name,recvbuf);
  int i;
  for(i = 0;i < num - 1;i++)
  {
   sendbuf[i] = recvbuf[num - i - 2]; //倒置
  }
  //sendbuf[num - 1] ='\0'; //可不要,系统会自动加上
  send(connectfd,sendbuf,strlen(sendbuf),0); //发送sendbuf中的数据到套接字connectfd
  }
  
  close(connectfd);
}
void *start_routine(void *arg)
{
 struct ARG *info;
 info = (struct ARG *)arg;
 process_cli(info->connfd,info->client);
 free(arg); //释放内存空间,对应前面的语句arg=malloc(sizeof(struct ARG));
 pthread_exit(NULL); //调用此函数主动退出线程
}
 
4.并发服务器:多路复用I/O
为了解决创建子进程带来的系统资源消耗,人们又想出了多路复用I/O模型.
首先介绍一个函数select
int select(int nfds,fd_set *readfds,fd_set *writefds,
                fd_set *except fds,struct timeval *timeout)
void FD_SET(int fd,fd_set *fdset)
void FD_CLR(int fd,fd_set *fdset)
void FD_ZERO(fd_set *fdset)
int FD_ISSET(int fd,fd_set *fdset)
一般的来说当我们在向文件读写时,进程有可能在读写出阻塞,直到一定的条件满足. 比如我们从一个套接字读数据时,可能缓冲区里面没有数据可读(通信的对方还没有 发送数据过来),这个时候我们的读调用就会等待(阻塞)直到有数据可读.如果我们不 希望阻塞,我们的一个选择是用select系统调用. 只要我们设置好select的各个参数,那么当文件可以读写的时候select回"通知"我们 说可以读写了. readfds所有要读的文件文件描述符的集合
writefds所有要的写文件文件描述符的集合
exceptfds其他的服要向我们通知的文件描述符
timeout超时设置.
nfds所有我们监控的文件描述符中最大的那一个加1
在我们调用select时进程会一直阻塞直到以下的一种情况发生. 1)有文件可以读.2)有文件可以写.3)超时所设置的时间到.
为了设置文件描述符我们要使用几个宏. FD_SET将fd加入到fdset
FD_CLR将fd从fdset里面清除
FD_ZERO从fdset中清除所有的文件描述符
FD_ISSET判断fd是否在fdset集合中
使用select后我们的服务器程序就变成了.
        初始话(socket,bind,listen);
        
    while(1)
        {
        设置监听读写文件描述符(FD_*);   
        
        调用select;
        
        如果是倾听套接字就绪,说明一个新的连接请求建立
             {
                建立连接(accept);
                加入到监听文件描述符中去;
             }
       否则说明是一个已经连接过的描述符
                {
                    进行操作(read或者write);
                 }
                        
        }               
多路复用I/O可以解决资源限制的问题.着模型实际上是将UDP循环模型用在了TCP上面. 这也就带来了一些问题.如由于服务器依次处理客户的请求,所以可能会导致有的客户 会等待很久.
实例:
//使用select()实现
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 1234
#define BACKLOG 5
#define MAXDATASIZE 1000
typedef struct CLIENT //定义结构体用于保存客户机信息与传来的数据
{
 int fd;
 char *name;
 struct sockaddr_in addr;
 char *data;
};
void process_cli(struct CLIENT *client,char *recvbuf,int len);
void savedata(char *recvbuf,int len,char *data);

int main(void)
{
 int i,maxi,maxfd,sockfd;
 int nready;
 ssize_t n;
 fd_set rset,allset; //文件描述符集合
 int listenfd,connectfd;
 struct sockaddr_in server;
 struct CLIENT client[FD_SETSIZE];
 char recvbuf[MAXDATASIZE];
 int sin_size;
 if((listenfd = socket(AF_INET,SOCK_STREAM,0)) == -1) //创建socket
 {
  printf("create socket error.\n");
  exit(1);
 }
 int opt = SO_REUSEADDR;
 setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); //设置socket状态
 bzero(&server,sizeof(server)); //清0
 server.sin_family = AF_INET;
 server.sin_port = htons(PORT);
 server.sin_addr.s_addr = htonl(INADDR_ANY); //自动获取IP
 if(bind(listenfd,(struct sockaddr *)&server,sizeof(struct sockaddr)) == -1)
 //服务器socket与地址绑定
 {
  printf("bind error.\n");
  exit(1);
 }
 if(listen(listenfd,BACKLOG) == -1) //socket监听
 {
  printf("listen error.\n");
  exit(1);
 }
 sin_size = sizeof(struct sockaddr_in);
 maxfd = listenfd; //maxfd存放最大的文件描述符
 maxi = -1;
 for(i = 0;i < FD_SETSIZE;i++) //初始化文件描述符为-1
 {
  client[i].fd = -1;
 }
 FD_ZERO(&allset); //文件描述符集合allset清零
 FD_SET(listenfd,&allset); //把listenfd加入文件描述符集合allset中
 while(1)
 {
  struct sockaddr_in addr;
  rset = allset; //rset文件描述符集合中的文件由select监视是否可被读取
  nready = select(maxfd+1,&rset,NULL,NULL,NULL);
  if(FD_ISSET(listenfd,&rset))
  //测试listenfd是否存在于rset中,若在
  {
   if((connectfd = accept(listenfd,(struct sockaddr *)&addr,&sin_size)) == -1) //接收
   {
    printf("accept error.\n");
    continue;
   }
   for(i = 0;i < FD_SETSIZE;i++)
   {
    if(client[i].fd < 0)
    {
     char *name;
     char *data;
     name = malloc(sizeof(char[MAXDATASIZE]));
     data = malloc(sizeof(char[MAXDATASIZE]));
     client[i].fd = connectfd;
     client[i].name = name;
     client[i].addr = addr;
     client[i].data = data;
     client[i].name[0] = '\0';
     client[i].data[0] = '\0';
     printf("You got a connect from %s \n",inet_ntoa(client[i].addr.sin_addr));
     break;
    }
   }
   if(i == FD_SETSIZE)
   {
    printf("to many clients\n");
   }
   FD_SET(connectfd,&allset); //把套接字connectfd加入文件描述符集合allset中
   if(connectfd > maxfd)
   {
    maxfd = connectfd; //因为maxfd是用于存放最大的文件描述符的
   }
   if(i > maxi)
   {
    maxi = i;
   }
   if(--nready <= 0)
   {
    continue;
   }
  }
  for(i = 0;i <= maxi;i++)
  {
   if((sockfd = client[i].fd) < 0)
   {
    continue;
   }
   if((n = recv(sockfd,recvbuf,MAXDATASIZE,0)) == 0)
   {
    close(sockfd);
    printf("client(%s) closed connection .User's data: %s\n",client[i].name,client[i].name,client[i].data);
    FD_CLR(sockfd,&allset); //将套接字sockfd从文件描述符集合allset中删除
    client[i].fd = -1;
   }
   else
   {
    process_cli(&client[i],recvbuf,n);
   }
   if(--nready <= 0)
   {
    break;
   }
  }
  
 }
 close(listenfd);
}

void process_cli(struct CLIENT *client,char *recvbuf,int len)
{
 char sendbuf[MAXDATASIZE];
 recvbuf[len - 1] == '\0';
 if(strlen(client->name) == 0)
 {
  memcpy(client->name,recvbuf,len);//从recvbuf中拷贝len字节数据到client->name中
  printf("client's name is %s\n",client->name);
  return;
 }
 printf("Received client (%s) message:\t%s\n",client->name,recvbuf);
 savedata(recvbuf,len,client->data); //调用savedata函数
 int i1;
 for(i1 = 0;i1 < len - 1;i1++)
 {
  sendbuf[i1] = recvbuf[len - i1 -2];
 }
 //sendbuf[len - 1] = '\0';
 send(client->fd,sendbuf,strlen(sendbuf),0);
}

void savedata(char *recvbuf,int len,char *data)
{
 int start = strlen(data);
 int i;
 for(i = 0;i < len;i++)
 {
  data[start + i] = recvbuf[i];
 }
}
 
5.并发服务器:UDP服务器
人们把并发的概念用于UDP就得到了并发UDP服务器模型. 并发UDP服务器模型其实是简单的.和并发的TCP服务器模型一样是创建一个子进程来处理的 算法和并发的TCP模型一样.
除非服务器在处理客户端的请求所用的时间比较长以外,人们实际上很少用这种模型.
 
6.客户端:UDP
上面的都是在说服务器端,为了调试,现在就给两个客户端的程序,当然客户端很简单了,我不想多说,直接看程序吧。
实例:
#include
#include
#include
#include
#include
#include
#include
#define PORT 1234
#define MAXDATASIZE 100
int main(int argc, char *argv[])
{
 int fd,numbytes;
 char buf[MAXDATASIZE+1];
 struct hostent *he;
 struct sockaddr_in server,reply;
 if(argc != 3)
 {
  printf("Usage:%s \n");
  exit(1);
 }
 if((he = gethostbyname(argv[1])) == NULL)  //把主机名或IPv4或IPv6转化为hostent结构的地址
 {
  printf("gethostbyname error.\n");
  exit(1);
 }
 if((fd = socket(AF_INET,SOCK_DGRAM,0)) == -1)
 {
  printf("socket error.\n");
  exit(1);
 }
 bzero(&server,sizeof(server));
 server.sin_family = AF_INET;
 server.sin_port = htons(PORT);
 server.sin_addr = *((struct in_addr *)he->h_addr); //in_addr为32位IPv4的地址结构
 sendto(fd,argv[2],strlen(argv[2]),0,(struct sockaddr *)&server,sizeof(struct sockaddr)); //发送argv[2]中的数据到套接字fd,此fd为与server通信的通道
 while(1)
 {
  int len;
  if((numbytes = recvfrom(fd,buf,MAXDATASIZE,0,(struct sockaddr *)&reply,&len)) == -1)
  {
       printf("recvfrom error.\n");
       exit(1);
  }
  if(len != sizeof(struct sockaddr) || memcmp((const void *)&server,(const void *)&reply,len) != 0)
      //当接受到的数据的地址reply与服务器的地址不相同时,说明该数据是来自别的服务器的
  {
       printf("recerve message from other server.\n");
       continue;
  }
  buf[numbytes] = '\0';
  printf("server message:%s\n",buf);
  break;
 
 }
 close(fd);
 }
7.客户端:TCP
实例:
#include
#include
#include
#include
#include
#include
#include
#define PORT 1234
#define MAXDATASIZE 100
int main(int argc,char *argv[])
{
 int fd,numbytes;
 char buf[MAXDATASIZE];
 struct hostent *he;
 struct sockaddr_in server;
 if(argc != 2)
 {
  printf("Usage:%s \n",argv[0]);
  exit(1);
 }
 if((he = gethostbyname(argv[1])) == NULL) //调用gethostbyname()进行地址转化,转化为hostent结构类型值,参数argc[1]可以为IPv4、IPv6地址或主机名.需要头文件为#include
 {
  printf("gethostbyname() error.\n");
  exit(1);
 }
 if((fd = socket(AF_INET,SOCK_STREAM,0)) == -1)//创建socket套接字,返回的套接字文件描述符赋给fd;出错判断
 {
  printf("socket() error.\n");
  exit(1);
 }
 bzero(&server,sizeof(server));//需要头文件#include .函数原型void bzero(void *s,size_t n);功能是把地址s所指内容中的前n个字节区域置零
 server.sin_family = AF_INET;
 server.sin_port = htons(PORT);
 server.sin_addr = *((struct in_addr *)he->h_addr);
 //根据gethostbyname()返回值he进行服务器地址结构体赋值转化,用于进行网络连接,使之能与服务器建立连接
 if(connect(fd,(struct sockaddr *)&server,sizeof(struct sockaddr)) == -1)//把服务器地址类型转化为内核所需类型,进行服务器连接
 {
  printf("connect() error\n");
  exit(1);
 }
 if((numbytes = recv(fd,buf,MAXDATASIZE,0)) == -1)//接收套接字fd中的数据,放入缓存buf中。函数原型ssize_t recv(int s,void *buf,size_t len,int flags),用于面向连接的socket
 {
  printf("revc() error\n");
  exit(1);
 }
 buf[numbytes] = "\0";
 printf("Server Message:%s\n ",buf);
 close(fd);
 
}
8.客户端:并发
实例:
//多进程处理连接
#include
#include
#include
#include
#include
#include
#include
#define PORT 1234
#define MAXDATASIZE 100
void process(FILE *fp,int sockfd);
char *getmessage(char *sendline,int len,FILE *fp);
int main(int argc,char **argv)
{
 int fd;
 struct hostent *he;
 struct sockaddr_in server;
 
 if(argc != 2)
 {
  printf("Usage:  %s \n",argv[0]);
  exit(1);
 }
 if((he = gethostbyname(argv[1])) == NULL) //把主机名或IPv4或IPv6转化为hostent结构的地址
 {
  printf("gethostbyname error.\n");
  exit(1);
 }
 if((fd = socket(AF_INET,SOCK_STREAM,0)) == -1) //创建socket
 { 
  printf("socket error.\n");
  exit(1);
 }
 bzero(&server,sizeof(server));//置0
 server.sin_family = AF_INET;  //设置为internet协议簇
 server.sin_port = htons(PORT);//设置端口号
 server.sin_addr = *((struct in_addr *)he->h_addr);//把hostent结构的地址转化为IPv4地址结构并赋值
 
 if(connect(fd,(struct sockaddr *)&server,sizeof(struct sockaddr)) == -1)
  //与服务器连接
 {
  printf("connect error.\n");
  exit(1);
 }
 process(stdin,fd);
 close(fd);
}
void process(FILE *fp,int sockfd)
{
 char sendline[MAXDATASIZE+1],recvline[MAXDATASIZE+1];
 int numbytes;
 printf("connected to server.\n");
 printf("Input name:");
 if(fgets(sendline,MAXDATASIZE,fp) == NULL) //把fp文件中的数据输入到sendline中,由主函数调用可知fp为标准输入(stdio)
  //fgets()回车也会接收并存到sendline中,并且会在最后一个字符后加'\0'
 {
  printf("exit.\n");
  return;
 }
 send(sockfd,sendline,strlen(sendline),0); //把sendline中的数据发送到套接字sockfd
 while(getmessage(sendline,MAXDATASIZE,fp) != NULL) //定义接收输入数据的自定义函数
 {
  send(sockfd,sendline,strlen(sendline),0);//可在此处加上语句bzero(recvline,MAXDATASIZE+1);这样可不要第74行(加'\0'那行)语句
  if((numbytes = recv(sockfd,recvline,MAXDATASIZE,0)) == 0)
  {
   printf("server terminated.\n");
   return;
  }
  recvline[numbytes] = '\0';//在末尾加'\0'再打印,防止recvline内存空间原本就有内容,否则会打印出多余数据
  printf("server message:\t%s\n",recvline);
 }
 printf("exit.\n");
}
char *getmessage(char *sendline,int len,FILE *fp)
{
 printf("Input string to server\n");
 return(fgets(sendline,MAXDATASIZE,fp)); //嵌套调用
}
 
 
 
 

 
阅读(779) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~