今天學習了一下linux環境的socket編程。先學習一下簡單的select模型。
一、select模型
是一種IO多路複用模型。一般網絡程序,使用accept、recv等接口。則服務端,在調用accept時,如果此時沒有客戶端連接時,則服務端需要等待。recv也是一樣。這種情況下,則服務端只能阻塞等待。select模塊,則是使用select函數去查詢你使用到的socket的狀態。比如,是否有客戶端連接,是否有客戶端發送數據。這樣可以,你可以針對有數據的socket進行操作。避免盲目等待。
二、接口介紹
2.1select函數
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
最主要的一個函數,就是select函數。該函數有5個參數。
maxfdp:是一個整數值,是指集合中所有文件描述符(socket)的範圍,即所有文件描述符的最大值加1。
readfds,writefds,errorfds都是fd_set類型的指針,是個socket的數組指針。
readfds:進行可讀操作查詢的文件描述符的集合,不關心讀操作,則可傳入NULL
writefds:進行可寫操作查詢的文件描述符的集合,不關心寫操作,則可傳入NULL。
errorfds:進行文件異常錯誤的查詢的文件描述符,也可以傳入NULL
timeout:第一,若將NULL以形參傳入,即不傳入時間結構,就是將select置於阻塞狀態,一定等到監視文件描述符集合中某個文件描述符發生變化爲止;第二,若將時間值設爲0秒0毫秒,就變成一個純粹的非阻塞函數,不管文件描述符是否有變化,都立刻返回繼續執行;第三,timeout的值大於0,這就是等待的超時時間,即select在timeout時間內阻塞,超時時間之內有事件到來就返回了,否則在超時後不管怎樣一定返回
返回值:如果有錯誤,則返回值爲-1,如果超時返回,則返回值爲0,如果,文件描述符裏面有變化,則返回值大於0
2.2 fd_set結構體操作
清空fd_set結構體
FD_ZERO(fd_set*);
向fd_set結構體添加一個文件描述符
FD_SET(int ,fd_set*);
判斷socket是否在某個集合內
FD_ISSET(fd, fdsetp)
三、示例代碼
1.服務端, 啓動一個端口,進行偵聽。循環查詢客戶端是否有數據發過來。如果有則讀取客戶端數據。
selectService.h文件代碼
#ifndef SELECT_SERVICE_H_
#define SELECT_SERVICE_H_
#include <list>
using namespace std;
class SelectService{
public:
explicit SelectService(unsigned short port)
: port_(port), listenSocket(0){}
bool beginRun();
private:
void runImpl();
void acceptOpe();
private:
list<int> socketList;
unsigned short port_;
int listenSocket;
};
#endif
selectService.cpp文件代碼
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "selectService.h"
#include <iostream>
#include <memory.h>
#include <netinet/in.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
using namespace std;
const size_t MAX_BUF_LEN = 1024;
const int MaxListenNum = 5;
int beginListen(unsigned short port){
struct sockaddr_in s_add,c_add;
int sfp = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sfp){
cout << "socket failed.error:" << strerror(errno) << endl;
return -1;
}
bzero(&s_add,sizeof(struct sockaddr_in));
s_add.sin_family=AF_INET;
s_add.sin_addr.s_addr=htonl(INADDR_ANY);
s_add.sin_port=htons(port);
if(-1 == bind(sfp,(struct sockaddr *)(&s_add), sizeof(struct sockaddr)))
{
cout << "bind failed. error:" << strerror(errno) << endl;
return -1;
}
if(-1 == listen(sfp, MaxListenNum))
{
cout << "listen failed. error:" << strerror(errno) << endl;
return -1;
}
return sfp;
}
void SelectService::runImpl()
{
fd_set rdfds;
struct timeval tv;
tv.tv_sec = 2;
tv.tv_usec = 500;
int socketSize = listenSocket;
FD_ZERO(&rdfds);
FD_SET(listenSocket, &rdfds);
for (list<int>::iterator iter=socketList.begin(); iter!=socketList.end(); ++iter)
{
FD_SET(*iter, &rdfds);
socketSize = *iter> socketSize ? *iter : socketSize;
}
int ret = select(socketSize+1, &rdfds, NULL, NULL, &tv);
if (ret < 0)
{
cout << "select error" << endl;
} else if(ret == 0 ){
cout << "select time out. socket size:" << socketSize << endl;
} else {
//查看是否有客戶端連接
if (FD_ISSET(listenSocket, &rdfds)){
//創建與客戶端的連接,加入到list裏
acceptOpe();
}
//判斷客戶端是否發請求過來
char buf[MAX_BUF_LEN] = {0};
for (list<int>::iterator iter=socketList.begin(); iter!=socketList.end(); )
{
memset(buf, 0, MAX_BUF_LEN);
//判斷socket是否可讀
if (FD_ISSET(*iter, &rdfds)){
ret = recv(*iter, buf, MAX_BUF_LEN, 0);
if (ret > 0)
{
cout << socketSize << ".socket:" << *iter << ". data:" << buf << endl;
strcpy(buf, "hello, this is select server.");
send(*iter, buf, strlen(buf), 0);
} else{
//客戶端斷開,關閉socket
close(*iter);
iter = socketList.erase(iter);
}
} else {
++iter;
}
}
}
}
void SelectService::acceptOpe()
{
///客戶端套接字
struct sockaddr client_addr;
socklen_t length = sizeof(client_addr);
///成功返回非負描述字,出錯返回-1
int clientConn = accept(listenSocket, (struct sockaddr*)&client_addr, &length);
if (clientConn < 0)
{
cout << "socket error:" << endl;
return ;
}
socketList.push_back(clientConn);
cout << "client is connect." << clientConn << endl;
}
bool SelectService::beginRun()
{
//創建一個socket,用於listen
listenSocket=beginListen(port_);
while (true){
runImpl();
}
return true;
}
main.cpp文件代碼#include "selectService.h"
int main(){
SelectService selectService(10241);
selectService.beginRun();
return 0;
}
2.客戶端代碼
用python寫了一個客戶端
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket, struct
import sys
import traceback
import json
import time
reload(sys)
sys.setdefaultencoding('utf-8')
ip = '192.168.86.39'
port = 10241
def init():
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.settimeout(2) # set time out
client_socket.connect((ip, port))
return client_socket
def cleanup(client_socket):
if client_socket:
try:
client_socket.close()
except:
print "catch an exception %s" % (traceback.format_exc())
if __name__ == '__main__':
req_str = "this is test."
client_socket = init()
for j in range(1000):
try:
ret_val = ""
client_socket.send('%s %d' % (req_str, j))
buf_ret = client_socket.recv(1024)
print buf_ret
time.sleep(1)
except socket.timeout, e:
print "socket timeout error: %s" %(traceback.format_exc())
except Exception, e:
print "socket send and recieve Error: %s" %(traceback.format_exc())
else:
pass