Windows Socket編程
一、基於TCP(面向連接)的socket編程
服務器端程序:
1、創建套接字(socket)。
2、將套接字綁定到一個本地地址和端口上(bind)。
3、將套接字設爲監聽模式,準備接受客戶請求(listen)。
4、等待客戶請求到來;當請求到來後,接受連接請求,返回一個新的對應於此次連接的套接字(accept)。
5、用返回的套接字和客戶端進行通信(send/recv)。
6、返回,等待另一客戶請求。
7、關閉套接字。
客戶端程序:
1、創建套接字(socket)。
2、向服務器發出連接請求(connect)。
3、和服務器端進行通信(send/recv)。
4、關閉套接字。
二、基於UDP(面向無連接)的socket編程
服務器端(接受端)程序:
1、創建套接字(socket)。
2、將套接字綁定到一個本地地址和端口上(bind)。
3、等待接收數據(recvfrom)。
4、關閉套接字。
客戶端(發送端)程序:
1、創建套接字(socket)。
2、向服務器發送數據(sendto)。
3、關閉套接字。
其實對於建立連接後,可以相互發送信息,無所謂誰是服務器,誰是客戶端。在開始的時候,一定是bind的一段,接收數據,這是死的。對於服務器和客戶是在開始的時候劃分的。
三、建立MFC的控制檯應用程序,編寫基於TCP/IP的服務器端應用。
1、int WSAStartup(
WORD wVersionRequested,
LPWSADATA lpWSAData
);
wVersionRequested的高位字節說明了socket的副版本號,低位字節說明了socket的主版本號。
LPWSADATA lpWSAData 指向WSADATA數據結構的指針,其中包含多個字段。
WSAStartup是socket應用程序調用的第一個函數,用於加載WindowsSockets版本號和WindowsSockets執行的細節參數。
具體代碼如下:
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 )return;
2、創建socket套接字
SOCKET socket(
int af,
int type,
int protocol
);
socket函數產生一個socket描述符和相關資源,和特定的協議聯繫。
af表示協議族,如果是TCP/IP or UDP協議,af等於AF_INET
type表示使用什麼類型的socket,有三種SOCK_STREAM,SOCK_DGRAM,SOCK_RAW
如果是編寫基於tcp的應用,使用SOCK_STREAM
如果編寫基於udp的應用,使用SOCK_DGRAM.
protocol默認設爲0。具體代碼:
SOCKET SockSrv=socket(AF_INET,SOCK_STREAM,0);
………127.0.0.1本地迴路地址,在本機器上發消息……………………
3、在服務器端將socket和地址、端口綁定。
int bind(
SOCKET s,
const struct sockaddr FAR *name,
int namelen
);
bind函數把產生的socket和指定的地址,端口聯繫起來,在服務器端使用。其中sockaddr(or SOCKADDR)用於存儲ip地址結構(服務器的地址和端口),具體定義爲:
struct sockaddr {
unsigned short sa_family;
char sa_data[14];
};
sa_family表示使用的socket地址族,sa_data[14]定義了socket地址結構的最大存儲單元。爲了更詳細的指定socket地址,可以使用SOCKADDR_IN 數據結構,它細分了TCP/IP Sockets 的地址字段,在程序中可以分別給每位字段賦預定的值,最後在bind時,進行SOCKADDR的強制轉換,因爲SOCKADDR和SOCKADDR_IN結構大小相等。
struct sockaddr_in{
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
struct in_addr {
union {
struct{
unsigned char s_b1,
s_b2,
s_b3,
s_b4;
} S_un_b;
struct {
unsigned short s_w1,
s_w2;
} S_un_w;
unsigned long S_addr;
} S_un;
};
在sockaddr中除sin_family外,其他都是網絡字節順序表示,因此需要顯示轉換數據的字節順序,把主機上的字節順序變換爲網絡的字節順序。其中u_short htons(
u_short hostshort
);
是轉換無符號短整型數。用於端口號的轉換。
u_long htonl(
u_long hostlong
);
是轉換無符號長整型數。用於S_addr的轉換。
namelen表示地址結構的長度。sizeof(sockaddr)即可得到。
具體代碼如下:
SOCKADDR_IN addrSRV;
addrSRV.sin_family=AF_INET;
addrSRV.sin_addr.S_un.S_addr=ADDR_ANY;
addrSRV.sin_port=htons(6000);
bind(SockSrv,(SOCKADDR*)&addrSRV,sizeof(SOCKADDR));
ADDR_ANY表示不詳細指定具體的網卡ip地址,它也不需要字節順序轉換,因爲值爲0,對於非零值則一定需要轉換。
4、設置socket爲監聽模式
int listen(
SOCKET s,
int backlog
);
s表示待設定的socket,backlog表示隊列的最大等待數。
具體代碼如下:
listen(SockSrv,10);
5、開始接受來自客戶端的請求
SOCKET accept(
SOCKET s,
struct sockaddr FAR *addr,
int FAR *addrlen
);
s是處於監聽狀態的socket,*addr是請求連接的客戶端的地址信息,*addrlen 是客戶端地址結構的長度。返回一個socket,然後可以使用返回的socket和客戶端通信。
具體代碼如下:
SOCKADDR_IN addrClient;
int len=sizeof(SOCKADDR);
SOCKET SockConn=accept(SockSrv,(SOCKADDR*)&addrClient,&len);
這裏的len必須進行初始化,否則會產生錯誤。
6、發送信息給客戶端
int send(
SOCKET s,
const char FAR *buf,
int len,
int flags
);
s是應答上客戶端的socket,即使用accept返回的socket。
*buf保存要傳送給客戶端的信息,是一個字符指針。
len保存字符數組的長度。
flags指定函數調用的方式。默認使用0。
具體代碼如下:
send(SockConn,"Welcome to www.sun.com!",sizeof("Welcome to www.sun.com!")+1,0);
7、接收來自客戶端的信息
int recv(
SOCKET s,
char FAR *buf,
int len,
int flags
);
s是和客戶端通信的socket
*buf存放接收的信息
len存放buf的大小
flags指定函數的調用方式,默認爲0。
具體代碼如下:
char recvBuf[100];
recv(SockConn,recvBuf,100,0);
printf("%s/n",recvBuf);
8、通信完畢後要關閉socket
int closesocket(SOCKET s );
具體代碼如下:
closesocket(SockConn);
還要在程序的最後關閉WSA
int WSACleanup (void);
所以int WSAStartup(
WORD wVersionRequested,
LPWSADATA lpWSAData
);
和int WSACleanup (void);成對出現。
對於以上的函數,要求包含Winsock2.h頭文件,同時連接時使用Ws2_32.lib庫(在工程->設置--->連接 中加入庫文件);否則,編譯運行會產生錯誤。
9、總結:
首先要了解socket編程的原理,在原理的指導下,每一個通信的步驟都對應Windows Socket的一個函數,調用函數完成預定的通信。
四、編寫基於TCP/IP的客戶端應用程序。
1、加載Windows Socket版本
int WSAStartup(
WORD wVersionRequested,
LPWSADATA lpWSAData
);
typedef struct WSAData {
WORD wVersion;
WORD wHighVersion;
char szDescription[WSADESCRIPTION_LEN+1];
char szSystemStatus[WSASYS_STATUS_LEN+1];
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR * lpVendorInfo;
} WSADATA, *LPWSADATA;
成功加載返回0。wVersionRequested的高位字節說明了socket的副版本號,低位字節說明了socket的主版本號。
2、創建socket套接字
SOCKET socket(
int af,
int type,
int protocol
);
SOCKET SockClient=socket(AF_INET,SOCK_STREAM,0);
3、使用socket套接字發送建立請求
int connect(
SOCKET s,
const struct sockaddr FAR *name,
int namelen
);
其中s是客戶端的套接字,*name存儲要連接到的服務器端的地址信息,在此函數之前要詳細說明各字段的值。
具體代碼如下:
SOCKADDR_IN SockSrv;
SockSrv.sin_family=AF_INET;
SockSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
SockSrv.sin_port=htons(6000); connect(SockClient,(SOCKADDR*)&SockSrv,sizeof(SOCKADDR));
inet_addr()是將ip地址字符串轉換成適合IN_ADDR 結構的ip地址。
4、接收來自服務器端的信息。
具體代碼如下:
char recvBuf[100];
recv(SockClient,recvBuf,100,0);
printf("%s/n",recvBuf);
5、發送信息給服務器。
send(SockClient,"I am chenlei!",sizeof("I am chenlei!")+1,0);
6、可以繼續發送和接收。
7、通信完畢關閉socket
int closesocket(SOCKET s );
具體代碼如下:
closesocket(SockClient);
還有相關操作和服務器端的一樣。
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/chenlei5662/archive/2008/08/20/2801228.aspx