Linux网络编程select模型的实现

73 篇文章 1 订阅
69 篇文章 0 订阅

简单的使用linux下的select模型实现了一个http的server

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <errno.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdlib.h>


#define LISTENQ 5
#define OPEN_MAX 1024
#define SERV_PORT  10088
#define MAX_LINE 1024
#define INFTIM -1


#define MAXEVENTS 1000

 


char szHtmlBuf[] = "HTTP/1.1 200 OK\r\n"
"Date: Sat, 05 Jan 2013 03:13:29 GMT\r\n"
"Vary: Accept-Encoding\r\n"
"Content-Type: text/html; charset=gb2312\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: 57\r\n"
"\r\n"
"<html> <head>欢迎光临</head> <body>屌丝逆袭季</body></html>";


fd_set fds;


void echo_srv(int clientFd)
{
//处理用户请求数据
char line[MAX_LINE];
printf( "开始读取数据");
int n = read(clientFd, line, sizeof(line));
if(n < 0)
{
//#define ECONNRESET 104  /* Connection reset by peer */
if(errno == ECONNRESET)
{
close(clientFd);
FD_CLR(clientFd, &fds);
printf("异常退出\n");
}
else
{
printf("网络异常");
exit(-1);
}
}
else if(n == 0)
{
close(clientFd);
FD_CLR(clientFd, &fds);
printf("正常退出\n");
}
else
{
line[n] = 0;
printf("接收到数据:%s\n", line);


write(clientFd, szHtmlBuf, sizeof(szHtmlBuf));
}
}


int main()
{
struct sockaddr_in cliaddr, servaddr;
int listenFd = socket(AF_INET, SOCK_STREAM, 0);
if( listenFd < 0)
{
printf("socket函数执行失败");
return 1;
}

servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//inet_aton('10.132.10.64', &(servaddr.sin_addr));
//servaddr.sin_addr.s_addr = inet_addr("10.132.10.64");
servaddr.sin_port = htons(SERV_PORT);
if(bind(listenFd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
{
  printf("bind函数执行失败");
return 1;
}

if(listen(listenFd, LISTENQ) < 0)
{
printf("listen函数执行失败");
return 1;
}
printf("listen函数执行成功\n");

//select 部分

int maxfd;
FD_ZERO(&fds);

do{
FD_SET(listenFd, &fds);
maxfd = listenFd + 1;
int nRead = 0;
if( (nRead = select(maxfd + 1, &fds, NULL, NULL, NULL)) < 0)
{
printf("select 失败\n");
exit(-1);
}
printf("select find data Change\n");
for(int i = 0; i <= maxfd && nRead > 0; i++)
{
if(!FD_ISSET(i, &fds))
{
continue;
}
--nRead;
if(i == listenFd)
{
socklen_t clilen = sizeof(cliaddr);
int connfd = accept(listenFd, (struct sockaddr*)&cliaddr, &clilen);
if(connfd < 0)
{
if ((errno != EAGAIN) && (errno != EWOULDBLOCK))
{
          printf( "fail to accept new client \n");
          continue;
}
}
printf("Ip: %s 到此一游\n", inet_ntoa(cliaddr.sin_addr));
FD_SET(connfd, &fds);
maxfd = (connfd > maxfd ? connfd : maxfd);
}
else
{
echo_srv(i);
}
}

}while(true);

return 0;
}

直接用浏览器进行测试即可:http://服务器Ip地址:10088

本篇文章来源于 Linux公社网站(www.linuxidc.com)  原文链接:http://www.linuxidc.com/Linux/2014-01/94777.htm

 

在网络上看到太多关于select i/o用法,都不是非常的详细。

经过摸索,把我实现的例子张贴上来,供大家参考。

描述:

服务器:实现并发tcp服务器,最多允许64 个客户端连接。

客户端:与服务器建立tcp连接,发送数据给服务器。服务器接收数据。

服务器代码:(红色为我的代码)

// SelectServerDlg.h : header file
//

#if !defined(AFX_SELECTSERVERDLG_H__3D5E9C67_8737_4E23_9734_0F196495ED30__INCLUDED_)
#define AFX_SELECTSERVERDLG_H__3D5E9C67_8737_4E23_9734_0F196495ED30__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

/
// CSelectServerDlg dialog
UINT proc(LPVOID lParam);

class CSelectServerDlg : public CDialog
{
// Construction
public:
 CSelectServerDlg(CWnd* pParent = NULL); // standard constructor
 
CWinThread *pThread;
 BOOL bRun;

// Dialog Data
 //{{AFX_DATA(CSelectServerDlg)
 enum { IDD = IDD_SELECTSERVER_DIALOG };
  // NOTE: the ClassWizard will add data members here
 //}}AFX_DATA

 // ClassWizard generated virtual function overrides
 //{{AFX_VIRTUAL(CSelectServerDlg)
 protected:
 virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
 //}}AFX_VIRTUAL

// Implementation
protected:
 HICON m_hIcon;

 // Generated message map functions
 //{{AFX_MSG(CSelectServerDlg)
 virtual BOOL OnInitDialog();
 afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
 afx_msg void OnPaint();
 afx_msg HCURSOR OnQueryDragIcon();
 afx_msg void OnClose();
 //}}AFX_MSG
 DECLARE_MESSAGE_MAP()
};

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_SELECTSERVERDLG_H__3D5E9C67_8737_4E23_9734_0F196495ED30__INCLUDED_)

————————————————————————————————————————

//SelectServerDlg.cpp代码

BOOL CSelectServerDlg::OnInitDialog()
{
 CDialog::OnInitDialog();
 
 // Add "About..." menu item to system menu.
 
 // IDM_ABOUTBOX must be in the system command range.
 ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
 ASSERT(IDM_ABOUTBOX < 0xF000);
 
 CMenu* pSysMenu = GetSystemMenu(FALSE);
 if (pSysMenu != NULL)
 {
  CString strAboutMenu;
  strAboutMenu.LoadString(IDS_ABOUTBOX);
  if (!strAboutMenu.IsEmpty())
  {
   pSysMenu->AppendMenu(MF_SEPARATOR);
   pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
  }
 }
 
 // Set the icon for this dialog.  The framework does this automatically
 //  when the application's main window is not a dialog
 SetIcon(m_hIcon, TRUE);   // Set big icon
 SetIcon(m_hIcon, FALSE);  // Set small icon
 
 // TODO: Add extra initialization here
 
WSAData wsadata;
 WORD wVersionRequest;
 wVersionRequest=MAKEWORD(2,2);
 if(WSAStartup(wVersionRequest,&wsadata)!=0)
  TRACE("没有DLL可用\n");
 if(LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2)
 {
  AfxMessageBox("socket dll 启动失败");
  WSACleanup();
 }
 pThread = AfxBeginThread(proc, this);
 return TRUE;  // return TRUE  unless you set the focus to a control
}

——————————————————————————————————————

//服务器线程

UINT proc(LPVOID lParam)
{
 try{
  fd_set rset;
  CSelectServerDlg *pDlg = (CSelectServerDlg*)lParam;
  struct sockaddr_in seraddr,cliaddr;
  int client[FD_SETSIZE],listensock;
  FD_ZERO(&rset);
  seraddr.sin_addr.s_addr = inet_addr("192.168.1.24");
  seraddr.sin_family = AF_INET;
  seraddr.sin_port = htons(6500);
  memset(seraddr.sin_zero, 0, 8);
  listensock = socket(AF_INET, SOCK_STREAM, 0);
  TRACE("listen socket :%d\n", listensock);
  bind(listensock, (struct sockaddr *)&seraddr, sizeof(seraddr));
  listen(listensock, 20);
  for(int i = 0; i<FD_SETSIZE; i++)
   client[i] = -1;
  pDlg->bRun = TRUE;
  int nReady;
  struct timeval timeout;
  int isset;
  int err;
  int maxfd;
  int connfd;
  int clilen;
  maxfd = listensock;
  char buf[1024];
  int recvbytes = 0;
  memset(buf, 0, 1024); 
  while(pDlg->bRun){
   Sleep(1);
   timeout.tv_sec = 0;
   timeout.tv_usec = 10;
   /*
   listen socket 加入fdset
   */
   FD_SET(listensock, &rset);
   for(int n = 0; n<FD_SETSIZE; n++)
   {
    if(client[n]>0){
     FD_SET(client[n], &rset);
     if(client[n] > maxfd)
      maxfd = client[n];
    }
    
   }

   nReady = select(maxfd+1, &rset, NULL, NULL, &timeout);
   if(nReady == 0)
    //TRACE("select fun time out\n");
    ;
   else if(nReady == -1)
   {
    TRACE("socket error\n");
    err = WSAGetLastError();
    switch(err)
    {
    case WSANOTINITIALISED:
     TRACE("WSANOTINITIALISED 未初始化\n");
     break;
    case WSAEFAULT:
     TRACE("WSAEFAULT\n");
     break;
    case WSAENETDOWN:
     TRACE("WSAENETDOWN\n");
     break;
    case WSAEINVAL:
     TRACE("WSAEINVAL\n");
     break;
    case WSAEINTR:
     TRACE("WSAEINTR\n");
     break;
    case WSAEINPROGRESS:
     TRACE("WSAEINPROGRESS\n");
     break;
    case WSAENOTSOCK:
     TRACE("WSAENOTSOCK\n");
     break;
    }
   }
   else
   {
    /*
    测试已经建立连接的socket
    */
    unsigned long ul = 1;
    for(int j = 0; j<FD_SETSIZE; j++)
    {
     if(client[j]>0)
     {
      memset(buf,0,1024);
      isset = FD_ISSET(client[j],&rset);
      if(isset)
      {
       //ioctlsocket(client[j], FIONBIO, &ul);
       recvbytes = recv(client[j], buf, 1024, NULL);
       if(recvbytes>0)
        TRACE("from socket: %d,接收字节数:%d,%s\n",client[j], recvbytes, buf);
       else if(recvbytes == 0)
       {
        closesocket(client[j]);
        TRACE("client socket :%d closed! \n",client[j]);
        client[j] = -1;
       }
      }
     }
    }
    /*
    测试是否有连接?
    */
    isset = FD_ISSET(listensock, &rset);
    FD_ZERO(&rset);
    if(isset)
    {
     TRACE("conncetion.... \n");
     clilen = sizeof(cliaddr);
     connfd = accept(listensock,(struct sockaddr *)&cliaddr, &clilen);
     TRACE("connection comes from ip:%d.%d.%d.%d\n",cliaddr.sin_addr.S_un.S_un_b.s_b1,
      cliaddr.sin_addr.S_un.S_un_b.s_b2,
      cliaddr.sin_addr.S_un.S_un_b.s_b3,
      cliaddr.sin_addr.S_un.S_un_b.s_b4);
     for(int s=0; s<FD_SETSIZE; s++){
      if(client[s]<0){
       client[s] = connfd;
       break;
      }
     }
     
     TRACE("con number:%d\n", s+1);
     TRACE("socket id:");
     for(int k = 0; k < FD_SETSIZE; k++)
     {
      if(client[k]>0)
       TRACE("%d,",client[k]);
     }
     TRACE("\n");
    }
    
   }
  }
 }

 catch (...) {
  TRACE("exception.........!!\n");
 }
 return TRUE;
}

——————————————————————————————————

//客户端代码:

BOOL CTcpClientDlg::OnInitDialog()
{
 CDialog::OnInitDialog();

 // Add "About..." menu item to system menu.

 // IDM_ABOUTBOX must be in the system command range.
 ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
 ASSERT(IDM_ABOUTBOX < 0xF000);

 CMenu* pSysMenu = GetSystemMenu(FALSE);
 if (pSysMenu != NULL)
 {
  CString strAboutMenu;
  strAboutMenu.LoadString(IDS_ABOUTBOX);
  if (!strAboutMenu.IsEmpty())
  {
   pSysMenu->AppendMenu(MF_SEPARATOR);
   pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
  }
 }

 // Set the icon for this dialog.  The framework does this automatically
 //  when the application's main window is not a dialog
 SetIcon(m_hIcon, TRUE);   // Set big icon
 SetIcon(m_hIcon, FALSE);  // Set small icon
 
 // TODO: Add extra initialization here

 WSAData wsadata;
 WORD wVersionRequest;
 wVersionRequest=MAKEWORD(2,2);
 if(WSAStartup(wVersionRequest,&wsadata)!=0)
  TRACE("没有DLL可用\n");
 if(LOBYTE(wsadata.wVersion)!=2||HIBYTE(wsadata.wVersion)!=2)
 {
  AfxMessageBox("socket dll 启动失败");
  WSACleanup();
 }
 serveraddr.sin_family=AF_INET;
 serveraddr.sin_port=htons(6500);
 serveraddr.sin_addr.S_un.S_un_b.s_b1=192;
 serveraddr.sin_addr.S_un.S_un_b.s_b2=168;
 serveraddr.sin_addr.S_un.S_un_b.s_b3=1;
 serveraddr.sin_addr.S_un.S_un_b.s_b4=24;
 clientsocket=socket(AF_INET,SOCK_STREAM,0);
 return TRUE;  // return TRUE  unless you set the focus to a control
}

void CTcpClientDlg::OnSend()
{
 // TODO: Add your control notification handler code here
 
nResult=connect(clientsocket,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
 if(nResult != SOCKET_ERROR)
 {
  TRACE("连接成功\n");
  char *szSend="123456789";
  nResult=sendto(clientsocket,szSend,9,0,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
  if(nResult != SOCKET_ERROR)
  {
   TRACE("发送成功\n");
  }
 }
 
}

void CTcpClientDlg::OnButton2()
{
 // TODO: Add your control notification handler code here
 
closesocket(clientsocket);
}

void CTcpClientDlg::OnButton3()
{
 // TODO: Add your control notification handler code here
 
char *szSend="123456789";
 nResult=sendto(clientsocket,szSend,9,0,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
 if(nResult != SOCKET_ERROR)
 {
  TRACE("发送成功\n");

 }
}

(完)

 

在很长的时间内,相比于Windows平台众多的选择,select模式都是linux平台下网络通信的不多选择之一(在epoll未出生之前)。这种模式在面对大量短连接的时候,比OCOT有高的效率,可以避免频繁的上下文切换。

     select系统调用是用来让我们的程序监视多个文件描述符(file descriptor)的状态变化的。程序会停在select这里等待,直到遇到以下情况之一select会退出:

     (1)     监控的句柄至少有一个发生变化,返回值大于1。

     (2)     等待时间已到,返回值等于0。

     (3)     收到信号退出,返回值小于0。

     select操作的内部会在指定的时间内,不断的检测受控集合中的套接字是否可读,可写或有错误,在select返回时只保留发生状态变化的套接字。通过判断select的返回值,就可以了解该调用是否有文件描述符可读或是select调用退出等信息。

     要想让程序运行,光有一个select的调用不是不够的,我们还需要一系列的配套数据结构和算法来帮助将套接字归类,判断是否有指定的套接字等等,结构体fd_set和宏FD_ZERO,FD_CLR,FD_SET,FD_ISSET等就是我们所需要的。通过定义一个fd_set的对象,例如可读集合,有相同需求的套接字都可以放置到这个对象中,可以通过FD_SET这个宏来完成,当然在这之间需要利用FD_ZERO将这个对象初始化。等完成了以上操作后,便会调用select查询是否有套接字可读。等待select完成后,如果有套接字可读,就可以利用FD_ISSET判断是否为监听套接字,转而执行accept操作。如果一个套接字已经关闭,就可以利用FD_CLR将其从fd_set对象中剔除。

    服务器端源代码:
 

 

  1 #include  < iostream >
  2 #include  < sys / socket.h >
  3 #include  < sys / types.h >
  4 #include  < arpa / inet.h >
  5 #include  < sys / select.h >
  6 #include  < sys / ioctl.h >
  7 using   namespace  std;
  8
  9 int  main()
 10 {
 11
 12   int server_sock = socket(AF_INET,SOCK_STREAM,0);
 13   if(server_sock < 0)
 14   {
 15      cout << "create socket error" << endl;
 16      return -1;
 17   }

 18
 19   struct sockaddr_in server_addr;
 20   memset(&server_addr,0,sizeof(server_addr));
 21   server_addr.sin_family = AF_INET;
 22   server_addr.sin_port = htons(5555);
 23   server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
 24
 25   if(bind(server_sock,(struct sockaddr*)&server_addr,sizeof(server_addr)) < 0)
 26   {
 27      cout << "bind socket error" << endl;
 28      close(server_sock);
 29      return -1;
 30   }

 31
 32   listen(server_sock,10);
 33
 34   fd_set read_set;
 35   fd_set test_set;
 36   FD_ZERO(&read_set);
 37   FD_SET(server_sock,&read_set);
 38
 39   struct timeval tm;
 40   tm.tv_sec = 5;
 41   tm.tv_usec = 500;
 42
 43
 44   while(true)
 45   {
 46       int nread = 0;
 47       test_set = read_set;
 48       int ret = select(FD_SETSIZE,&test_set,NULL,NULL,&tm);
 49
 50       if(ret < 0)
 51       {
 52           cout << "select error" << endl;
 53           close(server_sock);
 54           return -1;
 55       }

 56       else if(ret == 0)
 57       {
 58           //cout << "waitout" << endl;
 59           continue;
 60       }

 61       else
 62       {
 63           for(int fd=0; fd<FD_SETSIZE; ++fd)
 64           {
 65               if(FD_ISSET(fd,&test_set))
 66               {
 67                   //如果有新的连接到达
 68                   if(fd == server_sock)
 69                   {
 70                       int sock = accept(server_sock,NULL,NULL);
 71                       FD_SET(sock,&read_set);
 72                   }

 73                   else
 74                   {
 75                       ioctl(fd,FIONREAD,&nread);
 76
 77                       if(nread == 0//客户端已经关闭
 78                       {
 79                          close(fd);
 80                          FD_CLR(fd,&read_set);
 81                          cout << "client has removed " << fd << endl;
 82                       }

 83                       else
 84                       {
 85                          char buf[128];
 86                          recv(fd,buf,128,0);
 87                          cout << buf << endl;
 88                          send(fd,buf,strlen(buf)+1,0);
 89                       }

 90                   }

 91
 92               }

 93
 94           }

 95
 96       }

 97
 98   }

 99}

100

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值