现在的位置: 首页 > 综合 > 正文

linux下网络服务器模型以及使用时应该注意的问题

2018年03月16日 ⁄ 综合 ⁄ 共 10793字 ⁄ 字号 评论关闭

UDP编程应该注意的问题:
服务器端程序编写流程:
最常用的服务器模型.
创建一个socket句柄,然后绑定到本地端口上,然后创建一个线程进行接受和发送数据!(注意:一般是先接受,再发送,因为udp是面向无连接的服务,因此需要先通过recvfrom得到客户端的IP和端口信息(addr_remote),才能进行数据的收发)一般需要两个结构体struct sockaddr_in,一个是本地IP和端口信息,另一个是用于接受客户端的端口和IP;
第二点需要注意就是recvfrom和sendto的第六个参数类型是不一样的,recvfrom是socklen_t*而sendto是socklen_t,就这点错误让我折腾了差不多半天!唉!粗心大意真是害人呀!!!!!!!!!!!!!!!!!!!!!

3.1.      循环服务器:

循环服务器在同一个时刻只可以响应一个客户端的请求
3.1.1.    循环服务器之UDP服务器
UDP循环服务器的实现非常简单:UDP服务器每次从套接字上读取一个客户端的请求,处理, 然后将结果返回给客户机.
可以用下面的算法来实现.
socket(...);
bind(...);
while(1)
{
recvfrom(...);
process(...);
sendto(...);
}
**
**
因为UDP是非面向连接的,没有一个客户端可以老是占住服务端. 只要处理过程不是死循环, 服务器对于每一个客户机的请求总是能够满足.
**
3.1.2.    循环服务器之TCP服务器
TCP循环服务器的实现也不难:TCP服务器接受一个客户端的连接,然后处理,完成了这个客户的所有请求后,断开连接.
算法如下:
socket(...);
bind(...);
listen(...);
while(1)
{
accept(...);
while(1)
{
read(...);
process(...);
write(...);
}
close(...);
}
**
**
TCP循环服务器一次只能处理一个客户端的请求.只有在这个客户的所有请求都满足后, 服务器才可以继续后面的请求.这样如果有一个客户端占住服务器不放时,其它的客户机都不能工作了.因此,TCP服务器一般很少用循环服务器模型的.
**
3.2.      并发服务器
并发服务器在同一个时刻可以响应多个客户端的请求
**
3.2.1.    并发服务器之TCP服务器
为了弥补循环TCP服务器的缺陷,人们又想出了并发服务器的模型. 并发服务器的思想是每一个客户机的请求并不由服务器直接处理,而是服务器创建一个 子进程来处理.
算法如下:
socket(...);
bind(...);
listen(...);
while(1)
{
accept(...);
if(fork(..)==0)
{
while(1)
{
read(...);
process(...);
write(...);
}
close(...);
exit(...);
}
close(...);
}
**
**
TCP并发服务器可以解决TCP循环服务器客户机独占服务器的情况. 不过也同时带来了一个不小的问题.为了响应客户机的请求,服务器要创建子进程来处理. 而创建子进程是一种非常消耗资源的操作.
**
3.2.2.    并发服务器之多路复用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的一个例子
int use_select(int *readfd,int n)
{
fd_set my_readfd;
int maxfd;
int i;
**
maxfd=readfd[0];
for(i=1;i<n;i++)
     if(readfd>maxfd)
           maxfd=readfd;
while(1)
{
/* 将所有的文件描述符加入 */
FD_ZERO(&my_readfd);
for(i=0;i FD_SET(readfd,*my_readfd);
/* 进程阻塞 */
select(maxfd+1,& my_readfd,NULL,NULL,NULL);
/* 有东西可以读了 */
for(i=0;i if(FD_ISSET(readfd,&my_readfd))
{
/* 原来是我可以读了 */
we_read(readfd);
}
}
}
**
**
使用select后我们的服务器程序就变成了.
初始化(socket,bind,listen);
while(1)
{
设置监听读写文件描述符(FD_*);
调用select;
如果是倾听套接字就绪,说明一个新的连接请求建立
{
建立连接(accept);
加入到监听文件描述符中去;
}
否则说明是一个已经连接过的描述符
{
进行操作(read或者write);
}
**
}
多路复用I/O可以解决资源限制的问题.着模型实际上是将UDP循环模型用在了TCP上面. 这也就带来了一些问题.如由于服务器依次处理客户的请求,所以可能会导致有的客户 会等待很久.
**
3.2.3.    并发服务器之UDP服务器
人们把并发的概念用于UDP就得到了并发UDP服务器模型. 并发UDP服务器模型其实是简单的.和并发的TCP服务器模型一样是创建一个子进程来处理的 算法和并发的TCP模型一样.
除非服务器在处理客户端的请求所用的时间比较长以外,人们实际上很少用这种模型.
一个并发TCP服务器实例
#include “...”
#define MY_PORT 8888
int main(int argc ,char **argv)
{
int listen_fd,accept_fd;
struct sockaddr_in client_addr;
int n;
**
if((listen_fd=socket(AF_INET,SOCK_STREAM,0))<0)
{
printf(“Socket Error:%s/n/a”,strerror(errno));
exit(1);
}
**
bzero(&client_addr,sizeof(struct sockaddr_in));
client_addr.sin_family=AF_INET;
client_addr.sin_port=htons(MY_PORT);
client_addr.sin_addr.s_addr=htonl(INADDR_ANY);
n=1;
/* 如果服务器终止后,服务器可以第二次快速启动而不用等待一段时间 */
setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&n,sizeof(int));
if(bind(listen_fd,(struct sockaddr *)&client_addr,sizeof(client_addr))<0)
{
printf(“Bind Error:%s/n/a”,strerror(errno));
exit(1);
}
listen(listen_fd,5);
while(1)
{
accept_fd=accept(listen_fd,NULL,NULL);
if((accept_fd<0)&&(errno==EINTR))
continue;
else if(accept_fd<0)
{
printf(“Accept Error:%s/n/a”,strerror(errno));
continue;
}
if((n=fork())==0)
{
/* 子进程处理客户端的连接 */
char buffer[1024];
close(listen_fd);
n=read(accept_fd,buffer,1024);
write(accept_fd,buffer,n);
close(accept_fd);
exit(0);
}
else if(n<0)
printf(“Fork Error:%s/n/a”,strerror(errno));
close(accept_fd);
}
}
**
**
4.        数据流程
Server
Client
1. Establish a listening socket and wait for connections from clients.
**
**
2. Create a client socket and attempt to connect to server.
3. Accept the client's connection attempt.
**
4. Send and receive data.
4. Send and receive data.
5. Close the connection.
5. Close the connection.
**
**
5.        实例分析
总的来说,利用socket进行网络编程并不难,却有点繁琐,稍不留心,就会出错,在C++网络编程卷一中就举过这样一个例子
Error example of socket
#include <sys/types.h>
#include <sys/socket.h>
**
const int PORT_NUM=2007;
const int BUFSIZE=256;
**
int echo_server()
{
      struct sockaddr_in addr;
      int addr_len; //error 1 :未初始化addr_len
      char buf[BUFSIZE];
      int n_handle;
       //error 2: s_handle在windows平台上的类型为SOCKET,移植性不好
      int s_handle=socket(PF_UNIX,SOCK_DGRAM,0);
    
      if(s_handle==-1)      return -1;
      // error 3: 整个addr 结构要先清零
       // error 4: PF_UNIX应对应 PF_INET
      addr.sin_family=AF_INET;
       // error 5: PORT_NUM应使用网络字节顺序
      addr.sin_port=PORT_NUM;
      addr.sin_addr.addr=INSDDR_ANY;
**
      if(bind(s_handle,(struct sockaddr*) &addr,sizeof addr)==-1)
           return -1;
      // error 6: 未调用listen
       // error 7: 未加括号,导致运算符优先级问题
       // error 8: accept调用错误, 上面的socket调用应用SOCK_STREAM
      if(n_handle=accept(s_handle,(struct sockaddr*)&addr, &addr_len)!=-1)
      {
           int n;
            // error 9: read应该读取n_handle,而不是s_handle
           while((n=read(s_handle,buf,sizeof(buf))>0)
                 write(n_handle,buf,n);
       // error 9: 没有检查write返回值,有可能造成数据丢失
           close(n_handle);
      }
      return 0;    
}
**
所有凡是使用socket编程的程序中都想用一些相对简单的类来封装这些繁琐的接口调用
我也曾经做过这样的尝试
**
/*
* Copyright  2005 JinWei Bird Studio All rights reserved
*
* Filename: wf_socket.h
* Description: Test program of socket lib
*
* Version:1.0
* Create date: 08/19/2005
* Author: Walter Fan, walter.fan@gmail.com
*/
#include "wf_base.h"
**
#ifndef BACKLOG
#define BACKLOG 50
#endif
**
#ifndef HOSTLEN
#define HOSTLEN 256
#endif
**
class Socket
{
protected:  
    int m_nPort;
    int m_nSock;
    int m_nBacklog;
    char* m_szHost;
**
    bool m_bServer;
    fd_set m_fdSet;
    int m_fdNum;
public:
    Socket(int port);
    Socket(char* host,int port);
    virtual ~Socket();
    virtual int Wait()=0;//encapsulate select and accept
    virtual int Open()=0;//encapsulate socket,listen or connect
    int Close();//encapsulate close socket handle
    int GetSocketID();
    int CloseFD(int fd);//encapsulate close file handle
};
**
Socket::Socket(char* host,int port)
:m_szHost(host),m_nPort(port),m_bServer(false),m_fdNum(0)
{
    m_nSock=-1;
    m_nBacklog=BACKLOG;
    FD_ZERO(&m_fdSet);
    msg_trace("Socket construct as Client...");
}
**
Socket::Socket(int port)
:m_szHost("127.0.0.1"),m_nPort(port),m_bServer(true),m_fdNum(0)
{
    m_nSock=-1;
    m_nBacklog=BACKLOG;
    FD_ZERO(&m_fdSet);
    msg_trace("Socket construct as Server...");
}
**
Socket::~Socket()
{
    Close();
    msg_trace("Socket destruct...");
}
**
**
int Socket::Close()//encapsulate close socket handle
{
    if (m_bServer)
    {
          for (int fd = 0; fd <= m_fdNum; fd++)
          {  
                if (FD_ISSET(fd, &m_fdSet))
                     close(fd);
          }
    }
    else
    {  
          close(m_nSock);
    }
    return 0;
**
}
int Socket::GetSocketID()
{
    return m_nSock;
}
**
int Socket::CloseFD(int fd)//encapsulate close file handle
{
    int retval=0;
    retval=close(fd);
    if(retval<0)
          return retval;
    FD_CLR(fd, &m_fdSet);
    m_fdNum--;
    return retval;
}
**
//------------------------TCP --------------------//
class TCPSocket:public Socket
{
**
public:
    TCPSocket(int port):Socket(port){};
    TCPSocket(char* host,int port):Socket(host,port){};
  
    int Wait();
    int Open();
};
**
**
int TCPSocket::Open()
{
    int retval=0;
    //int     sock_id;           // the socket
    struct  sockaddr_in   saddr;   // build our address here
    struct  hostent        *hp;   // this is part of our          
      
    m_nSock = socket(AF_INET, SOCK_STREAM, 0);  // get a socket
    if ( m_nSock == -1 )
        return -1;
    if (m_nSock > m_fdNum)
          m_fdNum = m_nSock;
    //---set socket option---//
    int socket_option_value = 1;
    retval=setsockopt(m_nSock, SOL_SOCKET, SO_REUSEADDR,
                &socket_option_value, sizeof(socket_option_value));
    if(retval<0)
          return -1;
    //---build address and bind it to socket---//
**
    bzero((char *)&saddr, sizeof(saddr));   // clear out struct    
    gethostname(m_szHost, HOSTLEN);         // where am I ?        
    hp = gethostbyname(m_szHost);           // get info about host
    if (hp == NULL)
        return -1;                                        // fill in host part  
    bcopy((char *)hp->h_addr, (char *)&saddr.sin_addr, hp->h_length);
    saddr.sin_port = htons(m_nPort);        // fill in socket port
    saddr.sin_family = AF_INET ;            // fill in addr family
  
    if(m_bServer)
    {
        retval=bind(m_nSock, (struct sockaddr *)&saddr, sizeof(saddr));
        if (retval!= 0 )
            return -1;
  
        //---arrange for incoming calls---//
        retval=listen(m_nSock, m_nBacklog);
        if ( retval!= 0 )
            return -1;
        FD_SET(m_nSock,&m_fdSet);
    }
    else
    {
           retval=connect(m_nSock,(struct sockaddr *)&saddr, sizeof(saddr));
           //msg_trace("connect return "<<retval);
         if (retval!=0)
           return -1;
    }
    return m_nSock;
}
**
int TCPSocket::Wait()
{
    int retval=0;
    if(m_bServer)
    {
        fd_set fd_set_read;
        int fd,clientfd;
        struct sockaddr_un from;
        socklen_t from_len=sizeof(from);
      
        while(true)
        {
                //msg_trace("select begin...");
                retval=select(m_fdNum+1,&m_fdSet,NULL,NULL,NULL);
                //msg_trace("select return "<<retval);
                if(retval<0)
                     return -1;
                for(fd=0;fd<=m_fdNum;fd++)
                {
                     if(FD_ISSET(fd,&m_fdSet))
                     {
                           if(fd==m_nSock)
                           {
                                clientfd=accept(m_nSock,(struct sockaddr*)&from,&from_len);
                                //msg_trace("accept return "<<clientfd);
                                if(clientfd<0)
                                      return -1;
                                FD_SET(clientfd,&m_fdSet);
                                m_fdNum++;
                                continue;
                           }
                           else
                                return fd;
                     }  
                }
        }
                    
    }
    return retval;
}
**
int main(int argc, char *argv[])
{
  
    FILE* fp;
    time_t thetime;
    if(fork()==0)//client side
    {
    int sock, ret=0;
    char buf[100];
        TCPSocket oSock("127.0.0.1",1975);
        while((sock =oSock.Open())==-1);
        ret=write(sock,"hi,walter",10);
        if(ret<0) err_quit("write error");
        ret=read(sock,buf,sizeof(buf));
        if(ret<0) err_quit("read error");
        msg_trace("Client get "<<buf);
    }
    else//server side
    {
    int fd, ret=0;
    char buf[100];
        TCPSocket oSock(1975);
        oSock.Open();
        fd = oSock.Wait();
    if(fd<0)    err_quit("wait failed");
        ret=read(fd,buf,sizeof(buf));
        if(ret<0) err_quit("read failed");
        msg_trace("Server get "<<buf);
        ret=write(fd,"Good bye",10);
        if(ret<0) err_quit("wait failed");
        oSock.CloseFD(fd);
     }
     return 0;
}
**
**

抱歉!评论已关闭.