對於使用socket進行網絡編程說起來還是有規律可循的,弄懂了其模式流程,你感覺也就是那個樣。學習socket網絡編程時弄通幾個小例程對以後的學習是有很大的幫助的。
下面給出流式套接字的編程流程:
先從服務器端說起。服務器端先初始化Socket,然後與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待客戶端連接。在這時如果有個客戶端初始化一個Socket,然後連接服務器(connect),如果連接成功,這時客戶端與服務器端的連接就建立了。客戶端發送數據請求,服務器端接收請求並處理請求,然後把迴應數據發送給客戶端,客戶端讀取數據,最後關閉連接,一次交互結束。
對於基於UDP協議的socket編程模型服務端不需要有listen()和accept()過程,服務端和客戶端是對等的,在用socket()建立套接字後就可以使用sendto()和recvfrom()收發數據了。
下面給出一個完整的socket編程的服務端和客戶端的例程,注意裏面的防錯檢查程序的編寫很重要,不能少。最後不要忘記關閉socket和清理網絡庫!!
服務端:
#include<Winsock2.h>
#include<stdio.h>
#include<stdlib.h>
#define PORT 5000
//運行時注意連接ws2_32.lib庫(可以使用#pragma comment(lib,"ws2_32.lib"),也可以在VC++6.0的工程設置中鏈接。後者比較麻煩,每次運行都要鏈接一次)
void main()
{
int port = PORT;
WSADATA wsaData;
SOCKET sListen,sAccept;
int iLen; //客戶地址長度
int iSend; //發送數據長度
char buf[] = "HELLO,HOW ARE YOU!"; //指定需要發送的信息
struct sockaddr_in serv,client; //服務器,客戶的地址
if(WSAStartup(MAKEWORD(2,2),&wsaData) != 0) //初始化SOCKET庫
{
printf("Winsock load failed\n");
return;
}
sListen = socket(AF_INET,SOCK_STREAM,0); //創建套接字
if(sListen == INVALID_SOCKET)
{
printf("socket failed:%d\n",WSAGetLastError());
return;
}
//建立服務器的地址結構
serv.sin_family = AF_INET;
serv.sin_port = htons(port); //把一個雙字節主機字節順序的數據轉換爲網絡字節順序
serv.sin_addr.s_addr = htonl(INADDR_ANY);//把四字節主機字節順序轉換爲網絡字節順序,INADDR_ANY爲系統指定的IP地址
if(bind(sListen,(LPSOCKADDR)&serv,sizeof(serv)) == SOCKET_ERROR) //綁定套接字(綁定sListen和serv)
{
printf("bind() failed:%d\n",WSAGetLastError());
return;
}
if(listen(sListen,5) == SOCKET_ERROR) //進入監聽狀態
{
printf("listen()failed:%d\n",WSAGetLastError());
return;
}
iLen = sizeof(client); //初始化客戶地址長度
while(1) //進入循環,等待客戶連接請求
{
sAccept = accept(sListen,(struct sockaddr*)&client,&iLen); //接收客戶端的連接,該函數是阻塞的,同樣recv函數也是阻塞的,這也引出了I/O模型的作用。
if(sAccept == INVALID_SOCKET)
{
printf("accept() failed:%d\n",WSAGetLastError());
break;
}
//接收到的客戶端的端口號是那邊的系統分配的,輸出客戶IP地址和端口
printf("accepted client IP:[%s],port:[%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
iSend = send(sAccept,buf,sizeof(buf),0); //給連接的客戶發送信息
if(iSend == SOCKET_ERROR)
{
printf("send() failed:%d\n",WSAGetLastError());
break;
}
else if(iSend == 0)
break;
else
printf("send() byte:%d\n",iSend);
closesocket(sAccept);//關閉用於接收數據的socket,一定不能忘,不然會造成內存泄露
}
closesocket(sListen);////關閉用於監聽連接的socket,一定不能忘,不然會造成內存泄露
WSACleanup();//清理網絡庫
}
客戶端:
#include<Winsock2.h>
#include<stdio.h>
#define PORT 5000
#define BUFFER 1024
//運行時注意連接ws2_32.lib庫(可以使用#pragma comment(lib,"ws2_32.lib"),也可以在VC++6.0的工程設置中鏈接。後者比較麻煩,每次運行都要鏈接一次)
void main(int argc,char *argv[])
{
WSADATA wsaData;
SOCKET client;
int port = PORT;
int iLen; //從服務器接收的數據長度
char buf[BUFFER]; //接收數據的緩衝
struct sockaddr_in serv; //服務器端地址結構
memset(buf,0,sizeof(buf)); //接收數據緩衝區初始化
if(WSAStartup(MAKEWORD(2,2),&wsaData) != 0)
{
printf("Winsock load failed\n");
return;
}
//創建需要連接服務器的地址結構
serv.sin_family = AF_INET;
serv.sin_port = htons(port); //把一個雙字節主機字節順序的數據轉換爲網絡字節順序
serv.sin_addr.s_addr = inet_addr(argv[1]); //將命令行的IP地址轉化爲二進制表示的網絡字節順序IP地址
client = socket(AF_INET,SOCK_STREAM,0); //建立客戶端流套接字
if(client == INVALID_SOCKET)
{
printf("socket() failed:%d\n",WSAGetLastError());
return;
}
if(connect(client,(struct sockaddr*)&serv,sizeof(serv)) == INVALID_SOCKET) //請求與服務器建立TCP連接
{
printf("connect() failed:%d\n",WSAGetLastError());
return;
}
else
{
iLen = recv(client,buf,sizeof(buf),0); //從服務器接收數據
if(iLen == 0)
return;
else if(iLen == SOCKET_ERROR)
{
printf("recv() failed:%d\n",WSAGetLastError());
return;
}
printf("recv() data from server:%s\n",buf);
}
closesocket(client);//關閉socket,一定不能忘,不然會造成內存泄露
WSACleanup();//清理網絡庫
//讓程序等待
printf("press any key to continue");
while(1);
}
在網絡編程中經常出錯的一個地方就是網絡字節順序的問題。主要原因是計算機數據存儲有兩種字節優先順序:高位字節優先和低位字節優先。Internet上數據以高位字節優先順序在網絡上傳輸,所以對於在內部是以低位字節優先方式存儲數據的機器,在Internet上傳輸數據時就需要進行轉換,否則就會出現數據不一致。
下面是幾個字節順序轉換函數:
·htonl():把32位值從主機字節序轉換成網絡字節序
·htons():把16位值從主機字節序轉換成網絡字節序
·ntohl():把32位值從網絡字節序轉換成主機字節序
·ntohs():把16位值從網絡字節序轉換成主機字節序