1 前言
2 Socket編程
2.1 Socket通信機制
2.2 socket通信示例圖
2.3 Socket在不同平臺上的實現
2.3.1 Socket在Windows平臺中的實現
2.3.2 Socket在Linux/Unix平臺中的實現
2.3.3 可移植的啟動和結束調用代碼
2.3.4 其它移植問題
3 多線程編程
3.1 線程與進程的不同
3.2 線程沖突與數據保護
3.3 Win32中的線程
3.3.1 線程同步
3.3.2 創建線程
3.4 Linux/Unix中的線程
3.5 可移植的線程代碼
4 程序實例
前言
Socket編程特別是多線程編程是一個很大的課題,本文針對公司最近將要實現的下載版和網頁版的CPR和CPE兩個軟件來講解socket和多線程的可移植編程技術,所涉及的是其中較基礎的部分,以盡量滿足當前公司需要為準。
Socket編程
Socket通信機制通過socket可實現點對點或廣播通信程序,兩者之間的區別不大,編程時其程序流程所用代碼幾乎相同,不同的地方在于目標地址選擇的不同。本教材所舉實例為點對點的形式,即以客戶/服務器形式來實現一個socket通信程序,為描述方便,我們對兩點分別命名為Server和Client。Socket實現Server 與Client之間的通信的過程,非常象電信局的普通電話服務,為了更好的理解socket,以下就以電信局的電話服務作為類比來說明socket的通信機制:
* 電信局提供電話服務類似我們這的Server,普通電話用戶類似我們這的Client。
* 首先電信局必須建立一個電話總機。這就如果我們必須在Server端建立一個Socket(套接字),這一步通過調用socket()函數實現。
* 電信局必須給電話總機分配一個號碼,以便使用戶要撥找該號碼得到電話服務,同時接入該電信局的用戶必須知道該總機的號碼。同樣,我也在Server端也要為這一套接字指定一port(端口),并且要連接該Server的Client必須知道該端口。這一步通過調用bind()函數實現。
* 接下來電信局必須使總機開通并使總機能夠高效地監聽用戶撥號,如果電信局所提供服務的用戶數太多,你會發現撥打電信局總機老是忙音,通常電信局內部會使該總機對應的電話號碼連到好幾個負責交換的處理中心,在一個處理中心忙于處理當前的某個用戶時,新到用戶可自動轉到一下處理中心得到服務。同樣我們的Server端也要使自己的套接口設置成監聽狀態,這是通用listen()函數實現的,listen()的第二個參數是等待隊列數,就如同你可以指定電信局的建立幾個負責交換的處理中心。
* 用戶知道了電信局的總機號后就可以進行撥打請求得到服務。在Winsock的世界里做為Client端是要先用socket()函數建立一個套接字,然后調connect()函數進行連接。當然和電話一樣,如果等待隊列數滿了、與Server的線路不通或是Server沒有提供此項服務時,連接就不會成功。
* 電信局的總機接受了這用戶撥打的電話后負責接通用戶的線路,而總機本身則再回到等待的狀態。Server也是一樣,調用accept()函數進入監聽處理過程,Server端的代碼即在中處暫停,一旦Server端接到申請后系統會建立一個新的套接字來對此連接做服務,而原先的套接字則再回到監聽等待的狀態。
* 當你電話掛完了,你就可以掛上電話,彼此間也就離線了。Client和Server間的套接字的關閉也是如此;這個關閉離線的動作,可由Client端或Server端任一方先關閉,這也與電話局的服務類似。
從以上情況可以看出在服務器端建立一個套接字,進入監聽狀態到接收到一個客戶端的請求的過程如下:
socket()->bind()->listen()->accept()->recv()->send()
在客戶端則要簡單得多,其調用過程如下:
socket()->connect()->send()->recv()
socket通信示例圖
下圖顯示的通信方式用在許多場合,比如HTTP協議中用的就是這種方式。FTP、TELNET等協議也與此大致相同,不同之外在于FTP、TELNET這類協議會多次重復send()和recv()的調用。
Socket在不同平臺上的實現
Socket在Windows平臺中的實現
Socket在Windows中的實現稱為Winsock,核心文件是winsock.dll(或winsock32.dll)。這個動態鏈接庫負責提供標準的socket調用API和其它幾個特定的Windows平臺專用API,另外它還實現了適用于Windows消息機制的同步socket通信機制。在Winsock中所有非標準的socket調用均以WSA三個字母開頭命名。
由于Win32的各個平臺均支持線程,我采用同步socket通信機制來編寫Winsock程序不可取。一是多線程的系統中對同步通信有更好的解決辦法,二是同步通信機制程序沒有很好的兼容性。
Winsock中有兩個比較重要的函數就是WSAStartup()和WSACleanup(),它們的作用在于對Winsock動態鏈接庫進行一些初始化或清除工作。在一個進程中,沒有調用WSAStartup()之前將無法正確調用任何其它標準的socket函數。
Socket在Linux/Unix平臺中的實現
Socket最早是在Unix平臺上實現的,socket調用已是當今Unix平臺的標準系統調用。Linux的網絡部分模塊幾乎是原封不動的從Unix中遷移過來的,其函數的調用方式基本與Unix相同。
在Linux/Unix平臺中,如果你正在向一個套接字發信息,而遠端計算機又關閉了該端口,將產生一個SIGPIPE信號,該信號的默認處理過程將中止當前進程,為使我們的socket程序能夠健壯的運行,必須接管該信息的處理過程,一般情況使系統忽略該信號即可。
可移植的啟動和結束調用代碼
根據以上兩點所述的socket中不同平臺中的實現方式,可以編寫出可移植的socket初始化和結束調用代碼如下:
bool bsocket_init() //初始化socket 調用
{
#ifdef _WIN32
WSADATA wsad;
WORD wVersionReq;
wVersionReq=MAKEWORD(1,1); //清求不低于1.1版本的Winsock
if(WSAStartup(wVersionReq,&wsad))
return false;
return true;
#else
signal(SIGPIPE, SIG_IGN); //忽略SIGPIPE信號
return true;
#endif
}
void bsocket_cleanup() //結束socket 調用
{
#ifdef _WIN32
WSACleanup();
#else
signal(SIGPIPE, SIG_DFL); //恢復SIGPIPE信號
return;
#endif
}
其它移植問題
關閉socket
Windows中關閉socket的調用為closesocket(socket sock_in); Linux為close(socket sock_in);
Socket地址長度類型
Socket的許多調用中均用到socket地址長度做為參數,該參數在Windows中為int類型,在Linux中為socklen_t類型。
無效的socket
Windows中調用socket()和accept()時,如果返回的是無效的socket,則該值為INVALID_SOCKET,Linux中為-1。
調用錯誤返回
Windows中除上述的socket()和accept()以及幾個少數的調用外,大部分調用在出錯時返回值為SOCKET_ERROR,Linux中為-1。
多線程編程
線程與進程的不同
當把一個可執行程序裝載在內存中后,不管你是你是準備它或是已經執行它或是被暫停執行,它都被稱為一個進程。你可以稱一個進程為一個可執行程序在內存中的實例。雖然一個進程可以復制自己,也可以調用其它進程或與其它進程通信,全基本上進程是由操作系統直接管理的資源。每一個進程均有一個與其它進程獨立的操作系統運行環境,比如環境變量、當前目錄等。
線程是一種可執行體,它表示對CPU運行時間的占有權,線程也是操作系統中的一種資源,它的創建、中止最終要由操作系統來實現。但線程每一個線程更多表現為由一個進程直接來管理。每一個線程均有一個與其它線程不同的硬件運行環境,比如各CPU中通用寄存器和狀態寄存器的值。
一個進程的創建需要操作系統做大量工作,如設置環境變量、裝裁可執行代碼、分配進程資源等,進程間的切換也需做大量工作。而線程的創建的切換相對來說要簡單得多,操作也能夠以最快的速度來實現不同執行體之間的切換。
另一方面,由于所有線程共享一個進程空間,線程間的通信變得十分簡單;而進程之間由于各自內存相互隔離,進程之間通信只能通過管道、發送系統消息或信號來實現。
線程沖突與數據保護
由于同一進程中的線程由操作系統并發執行。當一個進程中的許多線程要修改某些公用變量時就存在數據保護問題。解決這個問題的通常辦法是向操作系統申請一個線程同步對象,操作系統只允許同時有一個少數個線程訪問受保護的數據。
Win32中的線程
線程同步
Win32中可用于線程同步的對象有CRITICAL_SECTION, Event, Mutex, Semaphore和Waitable timer等。 其中CRITICAL_SECTION比較適于解決在單一進程多線程的沖突問題。
與CRITICAL_SECTION有關的幾個函數為:
* InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
該函數初始化一個CRITICAL_SECTION。
* DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
該函數清除一個CRITICAL_SECTION。
* EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
該函數使本線程取得CRITICAL_SECTION控制權,代碼進入保護狀態。
* LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
該函數使本線程放棄CRITICAL_SECTION控制權,代碼退出保護狀態。
創建線程
Win32中最常用的創建線程的函數為CreateThread(),該函數的格式為:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId);
* lpThreadAttributes: 輸入參數。指定是否本次函數返回的線程句柄可以被子進程繼承,可以為NULL。在CPR/CPE兩個產品下載版和網頁版的開發過程代碼使用NULL應可滿足需要。
* dwStackSize : 輸入參數。指定指定的線程堆棧大小,可以為0,如果為0,使用默認堆棧大小。在CPR/CPE兩個產品下載版和網頁版的開發過程代碼使用0應可滿足需要。
* lpStartAddress : 輸入參數。指向線程函數,該函數包含線程的執行代碼。
* lpParameter : 輸入參數。指定要傳給線程的參數塊內存地址。
* lpThreadId : 輸出參數。該參數在調用結束后將包含被創建線程的ID。
Linux/Unix中的線程
當前各版本的Linux和Unix本身并不支持線程,但在Linux/Unix各版本均自帶pthread線程包及它們的開發運行頭文件和庫文件。
與Win32類似,pthread包中用于線程同步的對象也有多種,在我們的開發應用中,使用pthread_mutex_t即可滿足需求。與pthread_mutex_t相關的幾個函數為:
*
pthread_mutex_init(pthread_mutex_t * p, pthread_mutexattr_t * pa);
該函數初始化一個pthread_mutex_t,其中pa參數可以設置pthread_mutex_t的屬性。與Win32中多線程的機制稍有不同,在默認情況下,pthread中對同一線程的多次保護請求會造成互鎖,以我們產品開發的情況來看,要修改pthread的屬性方可滿足實際需要,但pthread中設置多線程同步屬性的方法的可移植性不高。對同一線程多次申請保護的問題可以記錄線程ID的辦法來解決。
* pthread_mutex_destroy(pthread_mutex_t * p);
清除一個已初始化的pthread_mutex_t。
* pthread_mutex_lock(pthread_mutex_t * p);
請求取得pthread_mutex_t控制權,進入代碼保護。
* pthread_mutex_unlock(pthread_mutex_t * p);
放棄一個pthread_mutex_t控制權,退出代碼保護。
可移植的線程代碼
(參見程序實例)
程序實例
#include
#ifdef _WIN32
#include
#define socklen_t int
#else
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define closesocket(_x) close(_x)
#endif
#ifdef _WIN32
#define THREAD_PROC WINAPI
#define THREAD_RETURN DWORD
#define THREAD_PARAM void *
#define THREAD_ID DWORD
#define CPO CRITICAL_SECTION
#define CPO_Init(_x) InitializeCriticalSection(&_x)
#define CPO_Dele(_x) DeleteCriticalSection(&_x)
#define CPO_Enter(_x) EnterCriticalSection(&_x)
#define CPO_Leave(_x) LeaveCriticalSection(&_x)
#else
struct CPO { pthread_mutex_t m; pthread_t t;};
#define THREAD_PROC
#define THREAD_RETURN void *
#define THREAD_PARAM void *
#define THREAD_ID pthread_t
#define CPO_Init(_x) { _x.t=0; pthread_mutex_init(&_x.m, NULL); }
#define CPO_Dele(_x) { pthread_mutex_destroy(&_x.m); }
#define CPO_Enter(_x) while(true) \
{ \
if(_x.t==0) \
{ \
pthread_mutex_lock(&_x.m); \
_x.t=pthread_self(); \
break;\
}\
if(pthread_self()==_x.t)\
break; \
pthread_mutex_lock(&_x.m); \
_x.t=pthread_self();\
break; \
}
#define CPO_Leave(_x) { pthread_mutex_unlock(&_x.m); _x.t=0; }
#endif
typedef THREAD_RETURN THREAD_PROC THREAD_FUNCTION(THREAD_PARAM thread_param);
#ifdef _WIN32
THREAD_ID bcreate_thread(THREAD_FUNCTION pfun, void * pparam)
{
THREAD_ID tid;
if(CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pfun, (LPVOID)pparam, 0, &tid)==NULL)
return (THREAD_ID)-1;
return tid;
}
#else
THREAD_ID bcreate_thread(THREAD_FUNCTION pfun, void * pparam)
{
THREAD_ID tid;
if(pthread_create(&tid, NULL, (void * (*)(void *))pfun, pparam)<1)
return (THREAD_ID)-1;
pthread_detach(tid);
return tid;
}
#endif
#ifndef SOCKET
#define SOCKET int
#endif
#ifndef SOCKET_ERROR
#define SOCKET_ERROR -1
#endif
#ifndef INVALID_SOCKET
#define INVALID_SOCKET -1
#endif
bool bsocket_init()
{
#ifdef _WIN32
WSADATA wsad;
WORD wVersionReq;
wVersionReq=MAKEWORD(1,1);
if(WSAStartup(wVersionReq,&wsad))
return false;
return true;
#else
signal(SIGPIPE, SIG_IGN);
return true;
#endif
}
void bsocket_cleanup()
{
#ifdef _WIN32
WSACleanup();
#else
signal(SIGPIPE, SIG_DFL);
return;
#endif
}
#define WEB_SERVER_PORT 90
#define WEB_SERVER_IP "127.0.0.1"
#define HTTP_HEAD "HTTP/1.0 200 OK\x0d\x0a""Content-type: text/html\x0d\x0a\x0d\x0a"
#define WELCOME_HTML "\n\nWelcome to my Website!
\ng_hits: %d\n\n\n"
CPO g_cpo;
int g_hits;
THREAD_RETURN THREAD_PROC do_accept(THREAD_PARAM param)
{
SOCKET sock=((SOCKET *)param)[0];
char ptmp[2048];
recv(sock, ptmp, 2048, 0);
CPO_Enter(g_cpo);
int a=g_hits;
sprintf(ptmp, WELCOME_HTML, a++);
g_hits=a;
CPO_Leave(g_cpo);
send(sock, HTTP_HEAD, strlen(HTTP_HEAD), 0);
send(sock, ptmp, strlen(ptmp), 0);
closesocket(sock);
return 0;
}
main(int argc,char ** argv)
{
char perror[1024];
SOCKET s,rs;
sockaddr_in sin,rsin;
socklen_t slen;
bool result=false;
perror[0]=0;
CPO_Init(g_cpo);
g_hits=0;
if(!bsocket_init())
{
strcpy(perror, "Can't init socket!");
goto error_out;
}
s=socket(PF_INET,SOCK_STREAM,0);
if(s==INVALID_SOCKET)
{
strcpy(perror, "Can't create socket!");
goto error_out;
}
sin.sin_family=AF_INET;
sin.sin_port=htons(WEB_SERVER_PORT);
sin.sin_addr.s_addr=inet_addr(WEB_SERVER_IP);
slen=sizeof(sin);
if(bind(s,(sockaddr *)&sin,slen)==SOCKET_ERROR)
{
strcpy(perror, "Can't bind socket!");
goto error_out;
}
if(listen(s,5)==SOCKET_ERROR)
{
strcpy(perror, "Can't listen on this socket!");
goto error_out;
}
printf("web server running......\n");
slen=sizeof(rsin);
while(true)
{
rs=accept(s,(sockaddr *)&rsin,&slen);
if(rs==INVALID_SOCKET)
{
strcpy(perror, "accept() a INVALID_SOCKET!");
break;
}
bcreate_thread(do_accept, &rs);
}
result=true;
error_out:
if(s!=INVALID_SOCKET) closesocket(s);
if(rs!=INVALID_SOCKET) closesocket(rs);
if(!result) { printf(perror); printf("\n"); }
CPO_Dele(g_cpo);
bsocket_cleanup();
return 0;
}
什么是Socket
Socket接口是TCP/IP網絡的API,Socket接口定義了許多函數或例程,程序員可以用它們來開發TCP/IP網絡上的應用程序。要學Internet上的TCP/IP網絡編程,必須理解Socket接口。
Socket接口設計者最先是將接口放在Unix操作系統里面的。如果了解Unix系統的輸入和輸出的話,就很容易了解Socket了。網絡的Socket數據傳輸是一種特殊的I/O,Socket也是一種文件描述符。Socket也具有一個類似于打開文件的函數調用Socket(),該函數返回一個整型的Socket描述符,隨后的連接建立、數據傳輸等操作都是通過該Socket實現的。常用的Socket類型有兩種:流式Socket(SOCK_STREAM)和數據報式Socket(SOCK_DGRAM)。流式是一種面向連接的Socket,針對于面向連接的TCP服務應用;數據報式Socket是一種無連接的Socket,對應于無連接的UDP服務應用。
Socket建立
為了建立Socket,程序可以調用Socket函數,該函數返回一個類似于文件描述符的句柄。socket函數原型為:
int socket(int domain, int type, int protocol);
domain指明所使用的協議族,通常為PF_INET,表示互聯網協議族(TCP/IP協議族);type參數指定socket的類型:SOCK_STREAM 或SOCK_DGRAM,Socket接口還定義了原始Socket(SOCK_RAW),允許程序使用低層協議;protocol通常賦值"0"。Socket()調用返回一個整型socket描述符,你可以在后面的調用使用它。
Socket描述符是一個指向內部數據結構的指針,它指向描述符表入口。調用Socket函數時,socket執行體將建立一個Socket,實際上"建立一個Socket"意味著為一個Socket數據結構分配存儲空間。Socket執行體為你管理描述符表。
兩個網絡程序之間的一個網絡連接包括五種信息:通信協議、本地協議地址、本地主機端口、遠端主機地址和遠端協議端口。Socket數據結構中包含這五種信息。
Socket配置
通過socket調用返回一個socket描述符后,在使用socket進行網絡傳輸以前,必須配置該socket。面向連接的socket客戶端通過調用connect函數在socket數據結構中保存本地和遠端信息。無連接socket的客戶端和服務端以及面向連接socket的服務端通過調用bind函數來配置本地信息。
bind函數將socket與本機上的一個端口相關聯,隨后你就可以在該端口監聽服務請求。bind函數原型為:
int bind(int sockfd,struct sockaddr *my_addr, int addrlen);
sockfd是調用socket函數返回的socket描述符,my_addr是一個指向包含有本機IP地址及端口號等信息的sockaddr類型的指針;addrlen常被設置為sizeof(struct sockaddr)。
struct sockaddr結構類型是用來保存socket信息的:
struct sockaddr {
unsigned short sa_family; /* 地址族, AF_xxx */
char sa_data[14]; /* 14 字節的協議地址 */
};
sa_family一般為AF_INET,代表Internet(TCP/IP)地址族;sa_data則包含該socket的IP地址和端口號。
另外還有一種結構類型:
struct sockaddr_in {
short int sin_family; /* 地址族 */
unsigned short int sin_port; /* 端口號 */
struct in_addr sin_addr; /* IP地址 */
unsigned char sin_zero[8]; /* 填充0 以保持與struct sockaddr同樣大小 */
};
這個結構更方便使用。sin_zero用來將sockaddr_in結構填充到與struct sockaddr同樣的長度,可以用bzero()或memset()函數將其置為零。指向sockaddr_in 的指針和指向sockaddr的指針可以相互轉換,這意味著如果一個函數所需參數類型是sockaddr時,你可以在函數調用的時候將一個指向sockaddr_in的指針轉換為指向sockaddr的指針;或者相反。
使用bind函數時,可以用下面的賦值實現自動獲得本機IP地址和隨機獲取一個沒有被占用的端口號:
my_addr.sin_port = 0; /* 系統隨機選擇一個未被使用的端口號 */
my_addr.sin_addr.s_addr = INADDR_ANY; /* 填入本機IP地址 */
通過將my_addr.sin_port置為0,函數會自動為你選擇一個未占用的端口來使用。同樣,通過將my_addr.sin_addr.s_addr置為INADDR_ANY,系統會自動填入本機IP地址。
注意在使用bind函數時需要將sin_port和sin_addr轉換成為網絡字節優先順序;而sin_addr則不需要轉換。
計算機數據存儲有兩種字節優先順序:高位字節優先和低位字節優先。Internet上數據以高位字節優先順序在網絡上傳輸,所以對于在內部是以低位字節優先方式存儲數據的機器,在Internet上傳輸數據時就需要進行轉換,否則就會出現數據不一致。
下面是幾個字節順序轉換函數:
·htonl():把32位值從主機字節序轉換成網絡字節序
·htons():把16位值從主機字節序轉換成網絡字節序
·ntohl():把32位值從網絡字節序轉換成主機字節序
·ntohs():把16位值從網絡字節序轉換成主機字節序
bind()函數在成功被調用時返回0;出現錯誤時返回"-1"并將errno置為相應的錯誤號。需要注意的是,在調用bind函數時一般不要將端口號置為小于1024的值,因為1到1024是保留端口號,你可以選擇大于1024中的任何一個沒有被占用的端口號。
連接建立
面向連接的客戶程序使用connect函數來配置socket并與遠端服務器建立一個TCP連接,其函數原型為:
int connect(int sockfd, struct sockaddr *serv_addr,int addrlen);
sockfd是socket函數返回的socket描述符;serv_addr是包含遠端主機IP地址和端口號的指針;addrlen是遠端地址結構的長度。connect函數在出現錯誤時返回-1,并且設置errno為相應的錯誤碼。進行客戶端程序設計無須調用bind(),因為這種情況下只需知道目的機器的IP地址,而客戶通過哪個端口與服務器建立連接并不需要關心,socket執行體為你的程序自動選擇一個未被占用的端口,并通知你的程序數據什么時候到達端口。
connect函數啟動和遠端主機的直接連接。只有面向連接的客戶程序使用socket時才需要將此socket與遠端主機相連。無連接協議從不建立直接連接。面向連接的服務器也從不啟動一個連接,它只是被動的在協議端口監聽客戶的請求。
listen函數使socket處于被動的監聽模式,并為該socket建立一個輸入數據隊列,將到達的服務請求保存在此隊列中,直到程序處理它們。
int listen(int sockfd, int backlog);
sockfd是Socket系統調用返回的socket 描述符;backlog指定在請求隊列中允許的最大請求數,進入的連接請求將在隊列中等待accept()它們(參考下文)。backlog對隊列中等待服務的請求的數目進行了限制,大多數系統缺省值為20。如果一個服務請求到來時,輸入隊列已滿,該socket將拒絕連接請求,客戶將收到一個出錯信息。
當出現錯誤時listen函數返回-1,并置相應的errno錯誤碼。
accept()函數讓服務器接收客戶的連接請求。在建立好輸入隊列后,服務器就調用accept函數,然后睡眠并等待客戶的連接請求。
int accept(int sockfd, void *addr, int *addrlen);
sockfd是被監聽的socket描述符,addr通常是一個指向sockaddr_in變量的指針,該變量用來存放提出連接請求服務的主機的信息(某臺主機從某個端口發出該請求);addrten通常為一個指向值為sizeof(struct sockaddr_in)的整型指針變量。出現錯誤時accept函數返回-1并置相應的errno值。
首先,當accept函數監視的socket收到連接請求時,socket執行體將建立一個新的socket,執行體將這個新socket和請求連接進程的地址聯系起來,收到服務請求的初始socket仍可以繼續在以前的 socket上監聽,同時可以在新的socket描述符上進行數據傳輸操作。
數據傳輸
send()和recv()這兩個函數用于面向連接的socket上進行數據傳輸。
send()函數原型為:
int send(int sockfd, const void *msg, int len, int flags);
sockfd是你想用來傳輸數據的socket描述符;msg是一個指向要發送數據的指針;len是以字節為單位的數據的長度;flags一般情況下置為0(關于該參數的用法可參照man手冊)。
send()函數返回實際上發送出的字節數,可能會少于你希望發送的數據。在程序中應該將send()的返回值與欲發送的字節數進行比較。當send()返回值與len不匹配時,應該對這種情況進行處理。
char *msg = "Hello!";
int len, bytes_sent;
……
len = strlen(msg);
bytes_sent = send(sockfd, msg,len,0);
……
recv()函數原型為:
int recv(int sockfd,void *buf,int len,unsigned int flags);
sockfd是接收數據的socket描述符;buf 是存放接收數據的緩沖區;len是緩沖的長度。flags也被置為0。recv()返回實際上接收的字節數,當出現錯誤時,返回-1并置相應的errno值。
sendto()和recvfrom()用于在無連接的數據報socket方式下進行數據傳輸。由于本地socket并沒有與遠端機器建立連接,所以在發送數據時應指明目的地址。
sendto()函數原型為:
int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);
該函數比send()函數多了兩個參數,to表示目地機的IP地址和端口號信息,而tolen常常被賦值為sizeof (struct sockaddr)。sendto 函數也返回實際發送的數據字節長度或在出現發送錯誤時返回-1。
recvfrom()函數原型為:
int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
from是一個struct sockaddr類型的變量,該變量保存源機的IP地址及端口號。fromlen常置為sizeof (struct sockaddr)。當recvfrom()返回時,fromlen包含實際存入from中的數據字節數。recvfrom()函數返回接收到的字節數或當出現錯誤時返回-1,并置相應的errno。
如果你對數據報socket調用了connect()函數時,你也可以利用send()和recv()進行數據傳輸,但該socket仍然是數據報socket,并且利用傳輸層的UDP服務。但在發送或接收數據報時,內核會自動為之加上目地和源地址信息。
結束傳輸
當所有的數據操作結束以后,你可以調用close()函數來釋放該socket,從而停止在該socket上的任何數據操作:
close(sockfd);
你也可以調用shutdown()函數來關閉該socket。該函數允許你只停止在某個方向上的數據傳輸,而一個方向上的數據傳輸繼續進行。如你可以關閉某socket的寫操作而允許繼續在該socket上接受數據,直至讀入所有數據。
int shutdown(int sockfd,int how);
sockfd是需要關閉的socket的描述符。參數 how允許為shutdown操作選擇以下幾種方式:
·0-------不允許繼續接收數據
·1-------不允許繼續發送數據
·2-------不允許繼續發送和接收數據
·均為允許則調用close ()
shutdown在操作成功時返回0,在出現錯誤時返回-1并置相應errno。
Socket 編程 windows到Linux代碼移植遇到的問題
1、一些常用函數的移植
http://www.vckbase.com/document/viewdoc/?id=1586
2、網絡 ------ 轉載 & 修改(待整理)
socket相關程序從windows移植到linux下需要注意的
1)頭文件
windows下winsock.h/winsock2.h
linux下sys/socket.h
錯誤處理:errno.h
2)初始化
windows下需要用WSAStartup
linux下不需要
3)關閉socket
windows下closesocket(...)
linux下close(...)
4)類型
windows下SOCKET
linux下int
如我用到的一些宏:
#ifdef WIN32
typedef int socklen_t;
typedef int ssize_t;
#endif
#ifdef __LINUX__
typedef int SOCKET;
typedef unsigned char BYTE;
typedef unsigned long DWORD;
#define FALSE 0
#define SOCKET_ERROR (-1)
#endif
5)獲取錯誤碼
windows下getlasterror()/WSAGetLastError()
linux下errno變量
6)設置非阻塞
windows下ioctlsocket()
linux下fcntl()
7)send函數最后一個參數
windows下一般設置為0
linux下最好設置為MSG_NOSIGNAL,如果不設置,在發送出錯后有可 能會導致程序退出。
8)毫秒級時間獲取
windows下GetTickCount()
linux下gettimeofday()
3、多線程
多線程: (win)process.h --〉(linux)pthread.h
_beginthread --> pthread_create
_endthread --> pthread_exit
關于這個話題網上流傳的是一個相同的版本,就是那個第一項是頭文件的區別,但后面列出的頭文件只有#include沒有(估計是原版的在不斷轉載的過程中有人不小心忘了把尖括號轉義,讓瀏覽器當html標記解析沒了)的那個。現在整理了一下,以后也會不斷補充內容。
1)頭文件
windows下winsock.h或winsock2.h
linux下netinet/in.h(大部分都在這兒),unistd.h(close函數在這兒),sys/socket.h(在in.h里已經包含了,可以省了)
2)初始化
windows下需要用WSAStartup啟動Ws2_32.lib,并且要用#pragma comment(lib,"Ws2_32")來告知編譯器鏈接該lib。
linux下不需要
3)關閉socket
windows下closesocket(...)
linux下close(...)
4)類型
windows下SOCKET
linux下int(我喜歡用long,這樣保證是4byte,因為-1我總喜歡寫成0xFFFF)
5)獲取錯誤碼
windows下getlasterror()/WSAGetLastError()
linux下,未能成功執行的socket操作會返回-1;如果包含了errno.h,就會設置errno變量
6)設置非阻塞
windows下ioctlsocket()
linux下fcntl(),需要頭文件fcntl.h
7)send函數最后一個參數
windows下一般設置為0
linux下最好設置為MSG_NOSIGNAL,如果不設置,在發送出錯后有可能會導致程序退出
8)毫秒級時間獲取
windows下GetTickCount()
linux下gettimeofday()
9)多線程
windows下包含process.h,使用_beginthread和_endthread
linux下包含pthread.h,使用pthread_create和pthread_exit
10)用IP定義一個地址(sockaddr_in的結構的區別)
windows下addr_var.sin_addr.S_un.S_addr
linux下addr_var.sin_addr.s_addr
而且Winsock里最后那個32bit的S_addr也有幾個以聯合(Union)的形式與它共享內存空間的成員變量(便于以其他方式賦值),而Linux的Socket沒有這個聯合,就是一個32bit的s_addr。遇到那種得到了是4個char的IP的形式(比如127一個,0一個,0一個和1一個共四個char),WinSock可以直接用4個S_b來賦值到S_addr里,而在Linux下,可以用邊向左移位(一下8bit,共四下)邊相加的方法賦值。
11)異常處理
linux下當連接斷開,還發數據的時候,不僅send()的返回值會有反映,而且還會像系統發送一個異常消息,如果不作處理,系統會出BrokePipe,程序會退出。為此,send()函數的最后一個參數可以設MSG_NOSIGNAL,禁止send()函數向系統發送異常消息。
搜索方向助理研究員
職位描述:
1.負責通用搜索引擎和垂直搜索引擎相關技術的研究;
2.相關性排序技術,anti-spam技術,spider技術,頁面分析技術,分詞技術,海量信息分類/聚類技術和web數據挖掘技術等研究。
職位要求:
1.對互聯網和搜索引擎技術有濃厚興趣;
2.有大規模數據挖掘、算法分析1年以上技術背景;
3.具有計算機及相關專業碩士及以上學;
4.有很強的分析問題和解決問題的能力,對數據很敏感,具有較好的技術創新能力;
5.精通Linux/Unix平臺上的C語言編程、多進程/多線程編程,熟悉SHELL編程;
6.具有超鏈分析/內容分析/排序算法/機器學習等相關方向經驗者優先;
7.有領導技術團隊經驗者優先;
8.對中國互聯網和搜索引擎產品現狀有一定理解。
招聘城市:北京、上海
工作地點:北京
該職位申請已結束。
WEB前端開發工程師
職位描述:
1.使用 JSP+HTML/CSS/Javascript 開發符合 W3C 標準的網站前端頁面;
2.積累并完善前端WEB前端開發框架,Javascript開發框架 ;
3.積極探索并積累前端開發模式和規范。
職位要求:
1.精通HTML/CSS/Javascript等前端技術,習慣于手寫符合W3C標準、兼容多種瀏覽器的前端頁面代碼,而不是依賴IDE;
2.熟練進行JAVA/JSP開發;
3.對JS的各種特性以及瀏覽器兼容性有豐富實戰經驗;
4.有Flash,ActionScript開發經驗者優先;
5.具備良好的團隊合作精神和積極主動的溝通意識;
6.具有強烈的進取心和求知欲,勇于挑戰。
招聘城市:北京、上海
工作地點:北京
該職位申請已結束。
Windows高級開發工程師
職位描述:
Windows平臺下的桌面軟件研發。
職位要求:
1.良好的溝通能力、敬業精神、學習能力;
2.熟練掌握C/C++語言、VC環境;
3.須有Windows開發經驗,熟練運用Win32 SDK、ATL、WTL等常用開發工具,熟悉COM、網絡編程;
4.熟悉音視頻編解碼技術者優先;
5.本科以上計算機相關專業學歷。
招聘城市:北京、上海
工作地點:北京
該職位申請已結束。
JAVA開發工程師
職位描述:
1.負責商業系統的架構設計與開發;
2.模塊設計與實現;現有模塊的分析與優化。
職位要求:
1.本科以上計算機相關學歷;
2.熟悉Linux/Unix操作系統,熟練使用shell、php等腳本編程語言;
3.熟練掌握Java語言,熟練掌握Eclipse或其他集成開發環境,精通Javascript等Web開發技術;
4.熟練掌握數據結構、常用算法;
5.對連接池/操作系統/圖形圖象處理/數據庫(其中一項)有較深入了解;
6.良好的溝通能力、團隊協作精神、敬業精神。
招聘城市:北京、上海
工作地點:北京
該職位申請已結束。
C/C++開發工程師
職位描述:
1.負責商業系統的架構設計與開發;
2.模塊設計與實現;現有模塊的分析與優化。
職位要求:
1.本科以上計算機相關學歷;
2.熟悉Linux/Unix操作系統,熟練使用shell、php等腳本編程語言;
3.熟練掌握C/C++語言、gcc/g++開發環境;
4.熟練掌握數據結構、常用算法;
5.熟練掌握Socket編程,對TCP/IP協議有充分理解。或對操作系統/圖形圖象處理/數據庫(其中一項)有較深入了解;
6.良好的溝通能力、團隊協作精神、敬業精神。
招聘城市:北京、上海
工作地點:北京
該職位申請已結束。
HoHo游戲開發引擎是針對2D游戲開發的專業引擎,擁有良好的系統構架、高效的圖像處理模塊、聲音以及網絡通訊等與游戲開發相關的所有功能;引擎使用了標準C++構造,以DirectX為基礎,可良好運轉于Windows98/me/2k/xp平臺,支持CPU的MMX指令優化。
引擎支持:
1、通過CDisplay基類實現的Direct3D與DirectDraw并用的多態機制,提供自動選擇恰當的進行方式;
2、引擎支持三種常用的圖形格式(BMP、TGA、JPG),支持alpha通道自動混合等;;
3、另外提供高級秀圖主題,驅駕硬體來加快位圖操作,提供額外的圖形操作(反轉等);
4、引擎還提供對圖象進行RLE壓縮、解壓繪制等;
5、數據管理方面支持直接Zip包的讀取,并有接口函式;
6、提供對常用聲音類型的支持,如:Midi、Wave、MP3。
7、提供AVI視頻文件的播放。
8、支持網絡連接通訊,使用多線程并行處理,完全可以滿足網絡游戲的需要。
本文簡單介紹了當前Windows支持的各種Socket I/O模型,如果你發現其中存在什么錯誤請務必賜教。
一:select模型
二:WSAAsyncSelect模型
三:WSAEventSelect模型
四:Overlapped I/O 事件通知模型
五:Overlapped I/O 完成例程模型
六:IOCP模型
老陳有一個在外地工作的女兒,不能經常回來,老陳和她通過信件聯系。他們的信會被郵遞員投遞到他們的信箱里。
這和Socket模型非常類似。下面我就以老陳接收信件為例講解 Socket I/O模型~~~
一:select模型
老陳非常想看到女兒的信。以至于他每隔10分鐘就下樓檢查信箱,看是否有女兒的信 ~~~~~
在這種情況下,"下樓檢查信箱" 然后回到樓上耽誤了老陳太多的時間,以至于老陳無法做其他工作。
select模型和老陳的這種情況非常相似:周而復始地去檢查...... 如果有數據......接收/發送 .......
使用線程來select應該是通用的做法:
procedure TListenThread.Execute;
var
addr : TSockAddrIn;
fd_read : TFDSet;
timeout : TTimeVal;
ASock,
MainSock : TSocket;
len, i : Integer;
begin
MainSock := socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
addr.sin_family := AF_INET;
addr.sin_port := htons(5678);
addr.sin_addr.S_addr := htonl(INADDR_ANY);
bind( MainSock, @addr, sizeof(addr) );
listen( MainSock, 5 );
while (not Terminated) do
begin
FD_ZERO( fd_read );
FD_SET( MainSock, fd_read );
timeout.tv_sec := 0;
timeout.tv_usec := 500;
if select( 0, @fd_read, nil, nil, @timeout ) > 0 then //至少有1個等待Accept的connection
begin
if FD_ISSET( MainSock, fd_read ) then
begin
for i:=0 to fd_read.fd_count-1 do //注意,fd_count <= 64,也就是說select只能同時管理最多64個連接
begin
len := sizeof(addr);
ASock := accept( MainSock, addr, len );
if ASock <> INVALID_SOCKET then
....//為ASock創建一個新的線程,在新的線程中再不停地select
end;
end;
end;
end; //while (not self.Terminated)
shutdown( MainSock, SD_BOTH );
closesocket( MainSock );
end;
二:WSAAsyncSelect模型
后來,老陳使用了微軟公司的新式信箱。這種信箱非常先進,一旦信箱里有新的信件,蓋茨就會給老陳打電話:喂,大爺,你有新的信件了!從此,老陳再也不必頻繁上下樓檢查信箱了,牙也不疼了,你瞅準了,藍天 ......不是,微軟~~~~~~~~
微軟提供的WSAAsyncSelect模型就是這個意思。
WSAAsyncSelect模型是Windows 下最簡單易用的一種Socket I/O模型。使用這種模型時,Windows會把網絡事件以消息的形勢通知應用程序。
首先定義一個消息標示常量:
const WM_SOCKET = WM_USER + 55;
再在主Form的private 域添加一個處理此消息的函數聲明:
private
procedure WMSocket(var Msg: TMessage); message WM_SOCKET;
然后就可以使用WSAAsyncSelect了:
var
addr : TSockAddr;
sock : TSocket;
sock := socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
addr.sin_family := AF_INET;
addr.sin_port := htons(5678);
addr.sin_addr.S_addr := htonl(INADDR_ANY);
bind( m_sock, @addr, sizeof(SOCKADDR) );
WSAAsyncSelect( m_sock, Handle, WM_SOCKET, FD_ACCEPT or FD_CLOSE );
listen( m_sock, 5 );
....
應用程序可以對收到WM_SOCKET消息進行分析,判斷是哪一個 socket產生了網絡事件以及事件類型:
procedure TfmMain.WMSocket(var Msg: TMessage);
var
sock : TSocket;
addr : TSockAddrIn;
addrlen : Integer;
buf : Array [0..4095] of Char;
begin
//Msg的WParam是產生了網絡事件的 socket句柄,LParam則包含了事件類型
case WSAGetSelectEvent( Msg.LParam ) of
FD_ACCEPT :
begin
addrlen := sizeof(addr);
sock := accept( Msg.WParam, addr, addrlen );
if sock <> INVALID_SOCKET then
WSAAsyncSelect( sock, Handle, WM_SOCKET, FD_READ or FD_WRITE or FD_CLOSE );
end;
FD_CLOSE : closesocket( Msg.WParam );
FD_READ : recv( Msg.WParam, buf[0], 4096, 0 );
FD_WRITE : ;
end;
end;
三:WSAEventSelect模型
后來,微軟的信箱非常暢銷,購買微軟信箱的人以百萬計數......以至于蓋茨每天 24小時給客戶打電話,累得腰酸背痛,喝蟻力神都不好使~~~~~~
微軟改進了他們的信箱:在客戶的家中添加一個附加裝置,這個裝置會監視客戶的信箱,每當新的信件來臨,此裝置會發出 "新信件到達"聲,提醒老陳去收信。蓋茨終于可以睡覺了。
同樣要使用線程:
procedure TListenThread.Execute;
var
hEvent : WSAEvent;
ret : Integer;
ne : TWSANetworkEvents;
sock : TSocket;
adr : TSockAddrIn;
sMsg : String;
Index,
EventTotal : DWORD;
EventArray : Array [0..WSA_MAXIMUM_WAIT_EVENTS-1] of WSAEVENT;
begin
...socket...bind...
hEvent := WSACreateEvent();
WSAEventSelect( ListenSock, hEvent, FD_ACCEPT or FD_CLOSE );
...listen...
while ( not Terminated ) do
begin
Index := WSAWaitForMultipleEvents( EventTotal, @EventArray[0], FALSE, WSA_INFINITE, FALSE );
FillChar( ne, sizeof(ne), 0 );
WSAEnumNetworkEvents( SockArray[Index-WSA_WAIT_EVENT_0], EventArray[Index-WSA_WAIT_EVENT_0], @ne );
if ( ne.lNetworkEvents and FD_ACCEPT ) > 0 then
begin
if ne.iErrorCode[FD_ACCEPT_BIT] <> 0 then
continue;
ret := sizeof(adr);
sock := accept( SockArray[Index-WSA_WAIT_EVENT_0], adr, ret );
if EventTotal > WSA_MAXIMUM_WAIT_EVENTS-1 then//這里WSA_MAXIMUM_WAIT_EVENTS 同樣是64
begin
closesocket( sock );
continue;
end;
hEvent := WSACreateEvent();
WSAEventSelect( sock, hEvent, FD_READ or FD_WRITE or FD_CLOSE );
SockArray[EventTotal] := sock;
EventArray[EventTotal] := hEvent;
Inc( EventTotal );
end;
if ( ne.lNetworkEvents and FD_READ ) > 0 then
begin
if ne.iErrorCode[FD_READ_BIT] <> 0 then
continue;
FillChar( RecvBuf[0], PACK_SIZE_RECEIVE, 0 );
ret := recv( SockArray[Index-WSA_WAIT_EVENT_0], RecvBuf[0], PACK_SIZE_RECEIVE, 0 );
......
end;
end;
end;
四:Overlapped I/O 事件通知模型
后來,微軟通過調查發現,老陳不喜歡上下樓收發信件,因為上下樓其實很浪費時間。于是微軟再次改進他們的信箱。新式的信箱采用了更為先進的技術,只要用戶告訴微軟自己的家在幾樓幾號,新式信箱會把信件直接傳送到用戶的家中,然后告訴用戶,你的信件已經放到你的家中了!老陳很高興,因為他不必再親自收發信件了!
Overlapped I/O 事件通知模型和WSAEventSelect模型在實現上非常相似,主要區別在 "Overlapped",Overlapped 模型是讓應用程序使用重疊數據結構(WSAOVERLAPPED),一次投遞一個或多個 Winsock I/O請求。這些提交的請求完成后,應用程序會收到通知。什么意思呢?就是說,如果你想從 socket上接收數據,只需要告訴系統,由系統為你接收數據,而你需要做的只是為系統提供一個緩沖區 ~~~~~
Listen線程和WSAEventSelect 模型一模一樣,Recv/Send線程則完全不同:
procedure TOverlapThread.Execute;
var
dwTemp : DWORD;
ret : Integer;
Index : DWORD;
begin
......
while ( not Terminated ) do
begin
Index := WSAWaitForMultipleEvents( FLinks.Count, @FLinks.Events[0], FALSE, RECV_TIME_OUT, FALSE );
Dec( Index, WSA_WAIT_EVENT_0 );
if Index > WSA_MAXIMUM_WAIT_EVENTS-1 then //超時或者其他錯誤
continue;
WSAResetEvent( FLinks.Events[Index] );
WSAGetOverlappedResult( FLinks.Sockets[Index], FLinks.pOverlaps[Index], @dwTemp, FALSE, FLinks.pdwFlags[Index]^ );
if dwTemp = 0 then //連接已經關閉
begin
......
continue;
end else
begin
fmMain.ListBox1.Items.Add( FLinks.pBufs[Index]^.buf );
end;
//初始化緩沖區
FLinks.pdwFlags[Index]^ := 0;
FillChar( FLinks.pOverlaps[Index]^, sizeof(WSAOVERLAPPED), 0 );
FLinks.pOverlaps[Index]^.hEvent := FLinks.Events[Index];
FillChar( FLinks.pBufs[Index]^.buf^, BUFFER_SIZE, 0 );
//遞一個接收數據請求
WSARecv( FLinks.Sockets[Index], FLinks.pBufs[Index], 1, FLinks.pdwRecvd[Index]^, FLinks.pdwFlags[Index]^, FLinks.pOverlaps[Index], nil );
end;
end;
五:Overlapped I/O 完成例程模型
老陳接收到新的信件后,一般的程序是:打開信封----掏出信紙 ----閱讀信件----回復信件 ......為了進一步減輕用戶負擔,微軟又開發了一種新的技術:用戶只要告訴微軟對信件的操作步驟,微軟信箱將按照這些步驟去處理信件,不再需要用戶親自拆信 /閱讀/回復了!老陳終于過上了小資生活!
Overlapped I/O 完成例程要求用戶提供一個回調函數,發生新的網絡事件的時候系統將執行這個函數:
procedure WorkerRoutine( const dwError, cbTransferred : DWORD; const
lpOverlapped : LPWSAOVERLAPPED; const dwFlags : DWORD ); stdcall;
然后告訴系統用WorkerRoutine函數處理接收到的數據:
WSARecv( m_socket, @FBuf, 1, dwTemp, dwFlag, @m_overlap, WorkerRoutine );
然后......沒有什么然后了,系統什么都給你做了!微軟真實體貼!
while ( not Terminated ) do//這就是一個Recv/Send線程要做的事情 ......什么都不用做啊!!!
begin
if SleepEx( RECV_TIME_OUT, True ) = WAIT_IO_COMPLETION then //
begin
;
end else
begin
continue;
end;
end;
六:IOCP模型
微軟信箱似乎很完美,老陳也很滿意。但是在一些大公司情況卻完全不同!這些大公司有數以萬計的信箱,每秒鐘都有數以百計的信件需要處理,以至于微軟信箱經常因超負荷運轉而崩潰!需要重新啟動!微軟不得不使出殺手锏 ......
微軟給每個大公司派了一名名叫"Completion Port"的超級機器人,讓這個機器人去處理那些信件!
"Windows NT小組注意到這些應用程序的性能沒有預料的那么高。特別的,處理很多同時的客戶請求意味著很多線程并發地運行在系統中。因為所有這些線程都是可運行的 [沒有被掛起和等待發生什么事], Microsoft意識到NT內核花費了太多的時間來轉換運行線程的上下文 [Context],線程就沒有得到很多CPU時間來做它們的工作。大家可能也都感覺到并行模型的瓶頸在于它為每一個客戶請求都創建了一個新線程。創建線程比起創建進程開銷要小,但也遠不是沒有開銷的。我們不妨設想一下:如果事先開好 N個線程,讓它們在那hold[堵塞 ],然后可以將所有用戶的請求都投遞到一個消息隊列中去。然后那N 個線程逐一從消息隊列中去取出消息并加以處理。就可以避免針對每一個用戶請求都開線程。不僅減少了線程的資源,也提高了線程的利用率。理論上很不錯,你想我等泛泛之輩都能想出來的問題, Microsoft又怎會沒有考慮到呢?"----- 摘自nonocast的《理解I/O Completion Port》
先看一下IOCP模型的實現:
//創建一個完成端口
FCompletPort := CreateIoCompletionPort( INVALID_HANDLE_VALUE, 0,0,0 );
//接受遠程連接,并把這個連接的socket句柄綁定到剛才創建的 IOCP上
AConnect := accept( FListenSock, addr, len);
CreateIoCompletionPort( AConnect, FCompletPort, nil, 0 );
//創建CPU數*2 + 2個線程
for i:=1 to si.dwNumberOfProcessors*2+2 do
begin
AThread := TRecvSendThread.Create( false );
AThread.CompletPort := FCompletPort;//告訴這個線程,你要去這個IOCP 去訪問數據
end;
OK,就這么簡單,我們要做的就是建立一個IOCP,把遠程連接的 socket句柄綁定到剛才創建的IOCP上,最后創建 n個線程,并告訴這n個線程到這個 IOCP上去訪問數據就可以了。
再看一下TRecvSendThread線程都干些什么:
procedure TRecvSendThread.Execute;
var
......
begin
while (not self.Terminated) do
begin
//查詢IOCP狀態(數據讀寫操作是否完成)
GetQueuedCompletionStatus( CompletPort, BytesTransd, CompletKey, POVERLAPPED(pPerIoDat), TIME_OUT );
if BytesTransd <> 0 then
....;//數據讀寫操作完成
//再投遞一個讀數據請求
WSARecv( CompletKey, @(pPerIoDat^.BufData), 1, BytesRecv, Flags, @(pPerIoDat^.Overlap), nil );
end;
end;
讀寫線程只是簡單地檢查IOCP是否完成了我們投遞的讀寫操作,如果完成了則再投遞一個新的讀寫請求。
應該注意到,我們創建的所有TRecvSendThread都在訪問同一個 IOCP(因為我們只創建了一個IOCP),并且我們沒有使用臨界區!難道不會產生沖突嗎?不用考慮同步問題嗎?
呵呵,這正是IOCP的奧妙所在。IOCP 不是一個普通的對象,不需要考慮線程安全問題。它會自動調配訪問它的線程:如果某個socket 上有一個線程A正在訪問,那么線程B 的訪問請求會被分配到另外一個socket。這一切都是由系統自動調配的,我們無需過問。
游戲開發所需知識
數學基礎:高等數學、線性代數、離散數學、數值分析等;
編程語言:c/c++、匯編(pascal、java可選);
編程工具:vc++6.0、delphi;
操作系統:windows api,系統工作原理;
硬件基礎:計算機工作原理,特殊硬件優化;
圖形基礎:計算機圖形學,圖形快速顯示算法,抖動算法;
多媒體: 波形文件回放,音頻設備控制,視頻圖像的解碼及播放;
壓縮加密:聲音、圖像壓縮解壓縮算法,加密算法;
游戲sdk: opengl,directx;
其它知識:人工智能,腳本算法,遺傳算法,模糊邏輯,物理建模(uml),軟件工程,編譯原理。
日本游戲培訓課程:
第一年:c語言,游戲設計,文章構成,windows開發,計算機系統導論,程序算法,游戲開發工具使用,情報數學,windows基礎;
第二年:c++語言,windows程序游戲設計,cg數學,java,playstation程序開發,可視化程序開發,數據通信,數據庫入門;
第三年:游戲開發演習,游戲理論,directx研究,vb游戲制作,java游戲制作,playstation研究;
游戲設計工具:
調試器: ollydbg(免費);
十六進制編輯: hex workshop;
安裝工具: install shield professioal
midi音樂: cakewalk;
聲效音樂: cooledit(或sound forge);
3d建模: 3dmax(或maya);
2d圖形程序: paint shop pro(或painter);
2d畫圖程序: coreldraw;
2d圖像處理: photoshop;
c/c++編譯器: vc++6.0;
要看開發什么游戲了,開發2D RPG,則不需要那么多知識,C++,DIRECTX,數據結構和計算機圖形常識,RPG游戲制作流程及常識,會用MFC或VB來開發地圖腳本編輯器,最后加上一些算法,A*,ALPHA BLENDING,斜45度地圖技術等就可以了
如果你想編游戲,而又有很多不清楚的問題,那請看這個:
1 語言編程:c/c++
2 編程基礎一定要好:數據結構,c/c++語言
2 IDE集中開發環境:visual studio .net 2003
3 游戲開發SDK用DirectX9
4 Win32 api開始的時候不能學的太多
5 可以不用MFC(如果你c++基礎好,MFC學起來很簡單)
6 編網絡游戲,使用winsock,通訊協議用TCP
7 多下載源代碼,觀看之
8 數學上的要求(其它基礎文化課類似):不需要了解算法的來歷、推導等,但一定要知道它們是干什么用的以及如何用
9 學習STL,必須C++要過關!否則會很難學。首先要學會如何用STL,再想深入的話,學習STL的內部代碼。STL首先從list,vector學起。
這里有一些經典推薦書籍介紹:
《微型計算機原理及應用》(第三版) 編著:鄭學堅 周斌 清華大學出版社
這是一本大學計算機基礎教材,雖然內容不是很新鮮,但基礎部分和匯編部分還是不錯的,并且價格方面,嘿嘿,借也可以借到,擁有這本書的學生真是太多了。
《C程序設計》(第二版) 作者:譚浩強 清華大學出版社
這本書不用我說,大家也都知道,流傳最廣泛的C語言教材了。如果看好了此書,C的功底一定不錯!
《數據結構(C語言版)》 編著:嚴蔚敏 吳偉民 清華大學出版社
又是一本大學經典教材,想對程序有深入了解,數據結構不可不看,學了他,你才能打開專業之門。
《C++編程思想》(第二版) 作者:Bruce Eckel 機械工業出版社
嘿嘿嘿,又是經典之作,想學C++和OO,看他,絕對沒錯,不用買別的C++入門書籍了!!!絕對經典。
《The C++ Standard Library--A Tutorial and Reference》 作者:Nicolai M. Josuttis
具有了一定的C++功底,該是看他的時候了,STL可是前人的思想精華。這本書主要講述如何使用STL.(我只有這本書的電子版)
《設計模式》 作者:Erich Gamma等著 機械工業出版社
把學習的方向和情況及時的記錄在博客上。
c and cpp 書籍和資料
數據結構和算法
IT筆試面試
windows編程
linux編程
web編程
網絡編程
多線程編程
ui編程
數據庫編程
面向過程編程
基于對象編程
面向對象編程
泛型編程
模板元編程
時間安排
1.20 ---- 3.1 之間
做完畢業設計 高性能計算 并行計算和分布式計算 網絡編程和多線程編程和數據結構和算法設計
visual c++ 網絡游戲設計和實現
linux系統下c編程
windows系統下編程 1.20 --- 2.10 之間
windows 網絡編程
win32多線程編程
asp.net sql server網站
中國象棋網絡版
數據結構和算法
linux下編程 2.10 --- 3.1 之間
linux下的網絡和多線程
lamp 網站一個
嵌入式開發 設備驅動開發
1.20 --- 3.1 之間
計算機和數學專業知識鞏固
以及英語和政治知識等其他知識的累積
http://www.shnenglu.com/mzty/ 學習c++技術
http://blog.chinaunix.net/u2/70469/index.html
http://www.w3school.com.cn/php/php_date.asp
http://www.w3school.com.cn/php/php_ref_string.asp
http://www.w3school.com.cn/php/index.asp
http://www.w3school.com.cn/html/html_lists.asp
Bluefish Editor
Terminate shell script