对于使用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位值从网络字节序转换成主机字节序