客户端程序过程
一个Socket客户端程序的典型过程如下。
- 客户端程序在运行后,首先需要使调用WSAStartup函数,确保进程加载socket应用程序所必须的环境和库文件,如Ws2_32.dll。
- 调用函数Socket创建SOCKET,在创建时需指定使用的网络协议、连接类型等。
- 填充SOCKADDR结构,指定服务端的地址、端口等。
- 调用connect函数连接到服务端。
- 如果连接成功,就可以使用send和recv函数发送和接收数据。
- 在数据传输完成后,可调用closesocket函数关闭Socket。
- 调用WSACleanup函数释放资源。
客户端程序
新建win32项目控制台程序 Win32Client项目:
// Win32Client.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib,"wsock32.lib")
#define DEFIP "127.0.0.1" //本地地址
#define DEFPORT 10000 //端口要大于1024
void main()
{
WSADATA wsaData;
LPVOID recvbuf;
if (WSAStartup(MAKEWORD(2,2),&wsaData) != NO_ERROR)
{
printf("Error at WSAStartup()\n");
}
SOCKET sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if (sockfd == INVALID_SOCKET)
{
printf("error at socket():%ld\n",WSAGetLastError());
closesocket(sockfd);
WSACleanup();
return;
}
// SOCKADDR_IN clientAddr;
// clientAddr.sin_family=AF_INET;
// clientAddr.sin_addr.s_addr= inet_addr("192.168.1.102");
// clientAddr.sin_port=htons(12345); //服务器会获取此端口
// if(bind(sockfd,(SOCKADDR*)&clientAddr,sizeof(clientAddr)) == SOCKET_ERROR)
// {
// printf("error at bind():%ld\n",WSAGetLastError());
// closesocket(sockfd);
// WSACleanup();
// return;
// }
SOCKADDR_IN serverAddr;
serverAddr.sin_family=AF_INET;
serverAddr.sin_addr.s_addr= inet_addr(DEFIP);
serverAddr.sin_port=htons(DEFPORT);
if(connect(sockfd,(SOCKADDR*)&serverAddr,sizeof(SOCKADDR)) == SOCKET_ERROR)
{
printf("error at connect()%ld\n",WSAGetLastError());
closesocket(sockfd);
WSACleanup();
return;
}
int bytesSend=0;
char sendBuf[32]="get information";
bytesSend=send(sockfd,sendBuf,strlen(sendBuf)+1,0);
if (bytesSend == SOCKET_ERROR)
{
printf("error at send()%ld\n",WSAGetLastError());
closesocket(sockfd);
WSACleanup();
return;
}
printf("Bytes send: %ld\n",bytesSend);
int bytesRecv=0;
char recvBub[2096]={0};
while( bytesRecv != SOCKET_ERROR)
{
bytesRecv=recv(sockfd,recvBub,strlen(recvBub)+1,0);
if (bytesRecv == 0)
{
printf("error at recv()%d",WSAGetLastError());
break;
}
printf("bytes recv:[%d] %s\n",bytesRecv,recvBub);
}
closesocket(sockfd);
WSACleanup();
}
函数解析
/*
WSAStartup():功能是加载ws2_32.dll等socket程序运行的环境。在程序初始化后,socket程序运行所依赖的动态链接库不一定已经加载,WSAStartup保证了Socket动态链接库的加载。
int WSAStartup(_in Word wVersionRequested,_out LPWSDATA lpWSAData);
wVersionRequested:是Socket程序库的版本,一般使用MAKEWORD(2,2)宏
lpWSAData:输出参数,指向WSADATA结构的指针,用于返回scoket库初始化的信息
返回值:0表示成功,
WSACleanup():与WSAStartup()的功能相反,WSACleanup释放ws2_32.dll库。
SOCKET socket( int af, int type, int protocol );
参数:int af:通信协议族(AF_UNIX,AF_INET等),AF_UNIX只能够用于单一的Unix系统进程间的通信,而AF_INET是针对Internet的,因此可以允许远程——本机之间的通信。AF:Address families(地址协议族)。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
int type:socket类型:SOCK_STREAM(TCP/IP),SOCK_DGRAM(UDP)
int protocol:指定协议(有IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议),由于我们指定了type,所以这个地方我们一般只要用0来代替就可以了
返回值:若成功返回SOCKET对象标识,SOCKET就是unsigned int;如失败,则返回INVALID_SOCKET,调用WSAGetLastError()可得知原因,所有WinSocket的函数都可以使用这个函数来获取失败的原因。
注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。
当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。
地址结构说明:
typedef struct sockaddr_in
{
short sin_family;//由于我们主要使用Internet,所以一般为AF_INET
u_short sin_port;//16位端口号,网络字节顺序
struct in_addr sin_addr;//32位IP地址,网络字节顺序
char sin_zero[8];//保留
}SOCKADDR_IN;
struct in_addr // Internet address.
{
uint32_t s_addr; // address in network byte order
};
//sin_:socket address internet
struct sockaddr是通用的套接字地址,而struct sockaddr_in则是internet环境下套接字的地址形式,二者长度一样,都是16个字节。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。一般情况下,需要把sockaddr_in结构强制转换成sockaddr结构再传入系统调用函数中。
字节转换函数
在网络上面有着许多类型的机器,这些机器在表示数据的字节顺序是不同的,比如i386芯片是低字节在内存地址的低端,高字节在高端,而alpha芯片却相反.为了统一起来,则有专门的字节转换函数.
unsigned long int htonl(unsigned long int hostlong)
unsigned short int htons(unisgned short int hostshort)
unsigned long int ntohl(unsigned long int netlong)
unsigned short int ntohs(unsigned short int netshort)
在这四个转换函数中,h代表host, n代表 network.s代表short l代表long
第一个函数的意义是将本机器上的long数据转化为网络上的long.其他几个函数的意义也差不多
int connect(SOCKET s,const struct sockaddr FAR * name,int namelen);
参数:s:socket的标识码
name:存储服务器的连接信息(协议族,服务器的ip,端口)
namelen:name的长度
返回值:成功返回0,失败返回SOCKET_ERROR
int send( SOCKET s, const char FAR *buf,int len, int flags );
参数:s:Socket 的识别码
buf:存放要传送的资料的暂存区
len buf:的长度
flags:此函数被调用的方式,可以设为 0或MSG_DONTROUTE及MSG_OOB组合
返回值:若成功则返回发送的资料的长度,否则返回SOCKET_ERROR
int recv( SOCKET s, char FAR *buf, int len, int flags );
参数:s:Socket 的识别码
buf:存放接收到的资料的暂存区
len buf:的长度
flags:此函数被调用的方式,可以设为 0或MSG_DONTROUTE及MSG_OOB组合
返回值:若成功则返回接收资料的长度,否则返回SOCKET_ERROR
int closesocket(SOCKET s);
*/
其结果:
发现结果有些小怪,若是
就好了。
其
bytesRecv=recv(sockfd,recvBub,
strlen(recvBub)+1,0); 改为 bytesRecv=recv(sockfd,recvBub,
sizeof(recvBub)+1,0);就可以了。