QQ登录

只需一步,快速开始

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 2345|回复: 10

网络模块的初步讨论

[复制链接]
发表于 2003-10-2 14:31:32 | 显示全部楼层 |阅读模式
根据之前我们讨论的,我们的网络模块使用面向对象的C++编写,在Demo版中不考虑加密/解密等其他因素,仅仅考虑消息的发送和接收。我想,暂时也不考虑随着人物的移动如何发送广播……以下是我这几天的研究成果。

套接字是一种网络API,用于不同进程的通信。它支持多种通信协议,并定义了多种类型,如SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_SEQPACKET等。其中最常用的两种是SOCK_STREAM、SOCK_DGRAM。SOCK_STREAM用于提供面向连接的传输,而SOCK_DGRAM用于提供面向无连接的传输。

采用TCP套接字可实现基于TCP/IP协议、面向连接的通信模式。TCP套接字的实现分为TCP服务器和TCP客户。其模式相对固定,我们仅需理解其基本过程,要编程可套用相应模板。TCP服务器的基本过程是:创建套接字socket()、绑定套接字bind()、监听listen()并接受连接请求accept()、读/写数据send()recv()和终止连接shutdown()。TCP客户的基本过程是:创建套接字socket()、连接服务器connect()、读写数据send()recv()和终止连接shutdown()。但却并不实用,无法满足复杂的网络应用。
[code:1]
/************************************************
       TCP服务器
************************************************/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

main()
{
  int sockfd,connect_sock;
  /*Create TCP socket*/
  if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
  {
     perror("Creating socket failed.");
     exit(1);
  }
  /*Bind socket to address */

......
  /*listen client's reqirement*/
......

  loop
  {
    /*accept connection*/
    if((connect_sock=accept(sockfd,NULL,NULL))==-1)
    {
      perrpr("Acception failed.");
      exit(1);
    }
......
  }
}

/************************************************
       TCP客户
************************************************/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

main()
{
  int sockfd;
  /*Create TCP socket*/
  if((sockfd=socket(AF_INET,SOCK_STREAM,,0))==-1)
  {
     perror("Creating socket failed.");
     exit(1);
  }
  /* Connect to server and receive data from the server.*/
......
}
[/code:1]

套接字的另一主要用途是UDP。采用UDP套接字可实现基于TCP/IP协议面向无连接的通信模式。于TCP套接字相比UDP套接字不能提供可靠的数据传输,但UDP套接字的实现更加简单,并且具有更高的传输效率。因而同样也得到广泛的应用,如DNS、NFS、SNMP。与TCP套接字相同,其实现也分为UDP服务器和UDP客户端。UDP服务器的基本过程包括:创建套接字、绑定套接字、读/写数据和关闭套接字。UDP客户的基本过程是:创建套接字、读写数据和关闭套接字。与TCP套接字不同,UDP套接字读/写函数,recvfrom()和sendto()函数都有与套接字地址相关的参数。对于UDP服务器而言,用该参数来区分不同的客户端。而对于客户端,则用此确定相应的服务器。——在Linux内部UDP的函数就是通过TCP函数实现功能的,所以我还是倾向于使用TCP函数,当然也不绝对,要听听大家的意见了……
[code:1]
/************************************************
       UDP服务器
************************************************/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

main()
{
  int sockfd;
  /*Create UDP socket*/
  if((sockfd=socket(AF_INET,SOCK_DGRAM,0))==-1)
  {
     perror("Creating socket failed.");
     exit(1);
  }
  /*Bind socket to address */

  loop
  {
    /*RECEIVE CLIENT'S DATA*/
    ...
    /*handle client's information*/
    ...
    /*send information to client*/
    ...
  }
  /*close socket*/
}

/************************************************
       UDP客户
************************************************/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

main()
{
  int sockfd;
  /*Create UDP socket*/
  if((sockfd=socket(AF_INET,SOCK_DGRAM,0))==-1)
  {
     perror("Creating socket failed.");
     exit(1);
  }
  /* send data to the server.*/
  ...
  /* receive data from the server.*/
  ...
  /*close UDP socket*/
  close(sockfd);
}
[/code:1]

Linux系统主要提供三种方式以实现并发服务器:进程、线程及I/O多路服用。
采用多进程实现并发服务器是一种最传统的方法。由于各进程有独立的地址空间,具有可靠性高的特点。但却导致系统开销大,切换时间长,并且进程间共享数据较为复杂。
[code:1]
/************************************************
       多进程并发服务器
************************************************/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>

main()
{
  int listenfd,connfd;
  pid_t pid;
  int BACKLOG=5;

  /* Create TCP socket */
  if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
  {
     perror("Creating socket failed.");
     exit(1);
  }
  /*Bind socket to address */
  bind(listenfd,...);
  /*listen client's reqirement*/
  listen(listenfd,BACKLOG);
  while(1)
  {
    /*accept connection*/
    if((connfd=accept(aockfd,NULL,NULL)==-1)
    {
      perrpr("Acception failed.");
      exit(1);
    }
    /*create child process to service the client.*/
    if(pid=fork())>0)
    {
    /*parent process*/
    close(connfd);
    ...
    continue;
  }
  else if (pid==0){
  /*child process*/
  close(listenfd);
  ...
  exit(0);
  }
  else{
    printf("fork error\n");
    exit(0);
  }
}
[/code:1]
虽然多线程具有系统开销小,切换速度快的等特点,但由于多个线程共用相同的内存区,从而造成多线程比多进程更严重的可重入问题。尤其是开发服务器的编程,如果不注意这个问题,会产生很多难以调试的bug。其根本解决方法是:为线程专有的数据分配专用的数据区。可采用系统提供的方法或主线程为每个新产生的线程分配数据区。
[code:1]
/************************************************
       多线程并发服务器
************************************************/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

/*function to be executed by the new thread */
void* start_routine(void * arg);

main()
{
  int listenfd,connfd;
  pthread_t thread;
  type arg;
  int BACKLOG=5;

  /* Create TCP socket */
  if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
  {
     perror("Creating socket failed.");
     exit(1);
  }
  /*Bind socket to address */
  bind(listenfd,...);
  /*listen client's connecting reqirement*/
  listen(listenfd,BACKLOG);
  while(1)
  {
    /*accept connection*/
    if((connfd=accept(aockfd,NULL,NULL)==-1)
    {
      perrpr("Acception failed.");
      exit(1);
    }
    /*create child process to service the client.*/
    .../*put parameters to arg*/
    if(pthread_create(&thread,NULL,start_routine,(void *)&arg)){
      /*handle exception*/  
      ...
      exit(0);
    }
  }
}
[/code:1]
I/O多路复用具有极小的系统开销,因此其性能很高,但其程序结构较为复杂。
[code:1]
/****************************************
   select()函数实现I/O多路复用
****************************************/
...
int fd,allfd[MAX];
fd_set fdRSet,fdWSet;
struct timewal timeout=...;
int maxfd=MAXSOCKET+1;
...
while(1){
  FD_ZERO(&fdRSet);
  FD_SET(fd,&fdTSet);
  FD_ZERO(&fdWSet);
  FD_SET(fd,&fdWSet);
  switch(select(maxfd,&fdTSet,&fdWset,&timeout)){
    case -1:handle exception;
    case 0:handle timeout;
    default:
      for(All of socket){
        if(FD_ISSET(allfd[i],&fdRSet)){
          Read from the socket.
          ...
          }
        if(FD_ISSET(allfd[i],&fdWSet)){
          Write to the socket.
          ...
          }
    ...
    }
  }
}
[/code:1]
由此看来,每种实现方法各有其有缺点,考虑到网络游戏的服务器端要有支持几百个客户端的连接,多进程实现并发服务器的方法可以不予考虑,至于后两种选择,由于我们准备采用面相对象的编程方法,以及先开发一个Demo版的游戏,我倾向于使用多线程的方法,不知大家有什么更好的建议?

在多进程/多线程环境中,线程及进程同步是十分重要的。该技术解决了有并发而产生的同步问题,使得并发服务器能可靠的运行。多线程并发服务器虽然有很多优点,但是存在一些问题,需要十分注意。除了前面讲到的线程安全性问题外,另一个重要的方面就是线程同步。
对于复杂的网络服务器,同步可能会异常复杂,处理不好就会导致系统的可靠性下降。尤其是多线程并发服务器,多个线程使用同一地址空间。虽然,线程间通信简单,却由于共享数据,则必须处理同步。在Linux系统中,提供互斥锁和条件变量来实现线程的同步。互斥锁用于保护共享数据的完整性,条件变量则用于协调多个线程的运行以提高运行效率。同时,线程退出的同步亦不能忽视,通常利用pthread_join()函数来实现。在处理同步时,要注意防止死锁。并且在长时间的操作(例如I/O)上还要尽量不要加锁,这样会对性能造成负影响,真的好麻烦啊:)给出一个实例吧:(程序太长,还是以后再说吧~~)

还有就是异常处理。写得有一些长,呵呵,希望朋友们有耐心看完……:)
异常处理是网络编程中最复杂的部分。异常主要有超时异常、服务器异常、客户异常……在实际的网络开发中,对异常的处理占有相当大的部分。这是由网络的特点决定的。在网络上,各主机系统的运行是独立的,网络本身也非常复杂,他包括了路由、信道、防火墙等等。网络的使用情况也随时变化,造成网络拥挤程度的不同。由于这些因素,经常造成通信中断、IP包传输延迟过长或丢失包等异常情况。如果处理不好,会直接影响系统性能。这些异常,在编程中,通常引起一些I/O操作的超时。
另一类异常是由本地系统引起的,通常是由于系统资源分配造成的。如socket()和bind()函数的调用异常。这些异常影响系统的稳定性,处理不好,可能导致系统崩溃,可能有点夸张……
无论是服务器还是客户,处理异常的关键是处理与网络I/O操作相关的系统调用。通常有如下三类处理方法:对于致命的错误,需要终止程序;对于一般性错误,处理完异常后,重新执行调用;对于超时异常,根据网络应用情况进行处理……I/O操作超时的处理主要用alarm()或select()函数来实现。

在网络应用开发中,随着需求的改变及性能的提高,程序变得越来越复杂,越来越大。从而导致编译和连接时间长。我们的网络模块到后来也不能超过这种情况,为更有效地重用代码,应该建立相应的类库。这也是C++的优势,也是sjinny选择C++的原因吧~为了更有效地重用代码,应该建立相应的类库。库是包括不同对象的文件。它可作为一个实体进行连接,因而可以极大地提高连接速度。据了解,Linux系统可以创建两种库:静态链接库和动态链接库。我想我们可以考虑建立自己地Socket类库,可极大地提高网络软件开发的效率,而且可开发出具有商业价值的类库,哈哈……

暂时就想到这么多……还有守护进程、多接口……过几天再讨论啦,不知道大家对我的想法有什么好的意见?   
 楼主| 发表于 2003-10-2 14:47:55 | 显示全部楼层
现在我在参考套接字Wrapper类源程序开发自己的socket类,过几天我会把初始版本放上来请大家查错、修改和扩充……
回复

使用道具 举报

发表于 2003-10-2 19:29:20 | 显示全部楼层
[quote:e6090d3807="deaboway"]现在我在参考套接字Wrapper类源程序开发自己的socket类,过几天我会把初始版本放上来请大家查错、修改和扩充…… [/quote]

我都忘了问了,你们原意把自己的代码使用GPL来发放吗?

我最近在用矢量画图软件画设计图,做了近一半了~~
回复

使用道具 举报

发表于 2003-10-3 00:34:22 | 显示全部楼层
文章写的不错!


对,应该坚持走GPL的道路(QPL也可以协商)。像LinQ那样封闭部分代码的做法,公社是不愿意继续提供支持的。
回复

使用道具 举报

 楼主| 发表于 2003-10-7 18:35:25 | 显示全部楼层
多谢夸奖……  感动ing   

做为一名自由软件的开发者,当然坚持GPL,Fujinsan兄,sjinny兄多虑了
回复

使用道具 举报

 楼主| 发表于 2003-10-7 19:36:09 | 显示全部楼层
sjinny, 不好意思,我不小心将你的MyWorld从我的项目中删掉了,麻烦你再把我加一下(在https://gro.clinux.org/projects/myworld/下的那个)。

还有,我打算将网络模块的类库先放到https://gro.clinux.org上,现在正在申请空间,过几天给出链接……
回复

使用道具 举报

发表于 2003-10-8 15:20:30 | 显示全部楼层
不错,懂了以前不懂的问题!十一我休息拉
回复

使用道具 举报

 楼主| 发表于 2003-10-12 22:04:09 | 显示全部楼层
rocklgk兄客气了,不过我一个人搞不定啦,还是需要你的支持呐!!
回复

使用道具 举报

发表于 2003-10-13 10:54:38 | 显示全部楼层
别谦虚拉,我只是做个参考而已,我真想全身心的投入,可时间啊,哎 ,郁闷ing
回复

使用道具 举报

 楼主| 发表于 2003-10-23 19:13:34 | 显示全部楼层
这几天我在准备笔试、面试……现在可以告一段落了,在等进一步通知,呵呵ing
回复

使用道具 举报

发表于 2005-4-18 13:08:26 | 显示全部楼层
直接用ace就好了,哪有这么麻烦的,再咋么封都封不过ace,别人都封了10年了
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

GMT+8, 2024-11-6 07:22 , Processed in 0.062581 second(s), 15 queries .

© 2021 Powered by Discuz! X3.5.

快速回复 返回顶部 返回列表