参考 : http://www.360doc.com/content/14/0318/15/15257968_361585987.shtml
http://www.cnblogs.com/churi/archive/2013/02/27/2935427.html
文末有代码
Socket也即是我们通常所说的套接字,其存在于通信区域中。通信区域也叫地址族,是一个抽象的概念,主要用于把所有通过套接字通信的进程共有的特性综合在一起,套接字通常之和同一区域的套接字交换数据(当然不同区域的通过转换也能实现)。而Winsows平台下的socket只支持一个通信区域:AF_INET(网际域),这个域被使用网际协议的进程使用。这在后面和socket有关的函数很有体现。
说到socket通信就不得不说下关于字节序的问题了。我们知道现在的硬件平台,对于数据在内存的存储顺序都是低位先存(也就是我们通常所说的主机字节序)。而在socket有关函数的参数都是要去以高位先存的存储方式的数据(网络字节序)。那么就需要一个从主机字节需到网络字节序的转换过程。
套接字有三种类型:
流式套接字(SOCK_STREAM),基于TCP传输协议,面向连接,可靠的传输方式。
数据报式套接字(SOCK_DGRAM),基于UDP,面向无连接,不可靠。
原始套接字(SOCK_RAW)。
Windows 的socket经历了众多版本的升级变迁,建议使用2以上的版本。
下面就是Windows Socket的编程实现了:
客户端/服务端
一,基于TCP(面向连接)的socket编程
服务端:
1,加载套接字库(WSAStartUp)
2,创建套接字(socket)
3,将套接字绑定到本机的一个地址和端口上(bind)
4,将套接字设为监听模式,准备接收客户端请求(listen)
5,等待客户请求到来;当请求到来后,接收连接请求,返回一个新的对应于此次连接的套接字(accept)
6,返回等待另一客户端的请求。
7,关闭套接字释放资源(closesocket)
8,卸载套接字库释放资源(WSACleanUp)
客户端:
1,加载套接字库(WSAStartUp)
2,创建套接字(socket)
3,向服务端发送请求(connect)
4,和服务端进行通信(send/recv)
5,关闭套接字释放资源(closesocket)
6,卸载套接字库释放资源(WSACleanUp)
二,基于UDP(面向无连接)的socket编程
服务端:
1,加载套接字库(WSAStartUp)
2,创建套接字(socket)
3,将套接字绑定到本机的一个地址和端口上(bind)
4,等待接收数据(recvfrom)
5,关闭套接字释放资源(closesocket)
6,卸载套接字库释放资源(WSACleanUp)
客户端:
1,加载套接字库(WSAStartUp)
2,创建套接字(socket)
3,向服务端发送数据(sento)
4,关闭套接字释放资源(closesocket)
5,卸载套接字库释放资源(WSACleanUp)
三,相关函数讲解:
加载套接字库并进行版本协商
Int WSAStartup(WORD wVersionRequested,
//请求的版本号,低字节代表主版本,高字节代表副版本,一般我们用MAKEWORD(x,y)//宏来指定版本号,如:MAKEWORD(2,1)代表2.1的版本
LPWSADATA lpWSAData
//out,一个WSADATA结构体指针,用于接收实际加载的套接字 库版本号
)
创建套接字
SOCKET socket(int af, //指定协议族,也即网际域,对于windows平台总是AF_INET 或 PF_INET
Int type,//指定要创建的套接字类型
Int protocol//指定协议类型,我们一般设为0,让他根据我们前两个参数自动设置
)
绑定到本机地址和端口
int bind(SOCKET s, //已创建的套接字描叙符
const struct sockaddr FAR *name, //要绑定的本机地址信息
Int namelen //第二个参数的长度
)
其中要注意第二个参数,由于第二个参数适用于所有网络协议,所以我们可以根据需要进行替换,如我们常常这样定义一个AF_INET 的地址信息:
SOCKADDR_IN SrvAddr;
SrvAddr.family=AF_INET;
SrvAddr.port=hotns(666); //其中666代表端口号
SrvAddr.sin_addr.S_un.S_addr=htonl(192.168.1.101);
//或SrvAddr.sin_addr.S_un.S_addr=INADDR_ANY;(指绑定到本机的任一快网卡上,并且其本身就是网络字节序,故无需转换,我推荐这种写法)
Inet_addr和inet_ntoa函数
Unsigned long Inet_addr(sconst char FAR * cp);
Inet_addr可以把一个点分十进制表示的IP(如:192.168.1.101)转换为unsinged long 类型的数据,且该返回值可以直接赋值给S_addr
Char FAR * inet_ntoa(struct in_addr in);
Inet_ntoa 从他函数的声明就知道他完成一个和inet_addr相反的转换 。
将指定的套接字设为见听听模式
Int listen(SOCKET s,int backlog);
第一个参数 s: 已经创建的套接字描述符
第二个参数 backlog 设置等待连接队伍的最大长度,注意:不是一个端口上可以同时连接的数目。
Accept函数
就收客服端发送的连接请求
SOCKET accept(
SOCKET s,
Struct sockaddr FAR * addr,// 返回请求连接方的IP和端口信息
Int FAR * addrlen
);
Send函数
通过一个已经建立连接的套接字发送数据
Int send(
SOCKET s,//注意:是以建立连接的套接字
Const char FAR * buf,//目的地IP和端口信息
Int len,
Int falgs//该设置影响发送行为,我们一般设为0
)
Recv 函数
Int recv(
SOCKET s,
Char FAR *buf,//发送数据的缓存地址
Int len,//发送数据长度
Int flags//该设置影响发送行为,我们一般设为0
)
Connect 函数
和一个特定的套接字建立连接
Int connect(
SOCKET s,
Const struct sockaddr FAR * name,//目的地址信息
Int namelen
)
Recvfrom函数
接受一次数据并保存数据源地址信息
Int recvfrom(
SOCKET s,
Char FAR*buf,
Int len,
Int flags,
Struct sockaddr FAR* from//用于保存数据源地址信息
Int FAR* fromlen
);
注意:最后一个参数是in,out类型,我们要在传参之前赋初始值
如:int len=Sizeof(SOCKADDR);
Sendto函数
向以一个特定的目的方发送数据
Int sendto(
SOCKET s,
Const char FAR * buf,//要发送的数据
Int len,//数据长度
Int flags,
Connect struct sockaddr FAR * to,//目的地址信息
Int tolen
)
前面我们说了在socket通信中都采用网络字节序(高位先存),那么在实际的编程过程中必然少不了转换函数,这里介绍两个,htons和htonl
U_short htons(u_short hostshort);
U_long htonl(u_long hostlong);
功能:把一个无符号短型/长性主机字节序的数据转换为网络字节序
代码在visual stdio 2013中编译并成功运行:
tcp服务器端:
#include "stdafx.h"
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
int main(int argc, char* argv[])
{
//初始化WSA
WORD sockVersion = MAKEWORD(2,2);
WSADATA wsaData;
if(WSAStartup(sockVersion, &wsaData)!=0)
{
return 0;
}
//创建套接字
SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(slisten == INVALID_SOCKET)
{
printf("socket error !");
return 0;
}
//绑定IP和端口
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf("bind error !");
}
//开始监听
if(listen(slisten, 5) == SOCKET_ERROR)
{
printf("listen error !");
return 0;
}
//循环接收数据
SOCKET sClient;
sockaddr_in remoteAddr;
int nAddrlen = sizeof(remoteAddr);
char revData[255];
while (true)
{
printf("等待连接...\n");
sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
if(sClient == INVALID_SOCKET)
{
printf("accept error !");
continue;
}
printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
//接收数据
int ret = recv(sClient, revData, 255, 0);
if(ret > 0)
{
revData[ret] = 0x00;
printf(revData);
}
//发送数据
char * sendData = "你好,TCP客户端!\n";
send(sClient, sendData, strlen(sendData), 0);
closesocket(sClient);
}
closesocket(slisten);
WSACleanup();
return 0;
}
客户端:
#include "stdafx.h"
#include <WINSOCK2.H>
#include <STDIO.H>
#pragma comment(lib,"ws2_32.lib")
int main(int argc, char* argv[])
{
WORD sockVersion = MAKEWORD(2,2);
WSADATA data;
if(WSAStartup(sockVersion, &data) != 0)
{
return 0;
}
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sclient == INVALID_SOCKET)
{
printf("invalid socket !");
return 0;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(8888);
serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{
printf("connect error !");
closesocket(sclient);
return 0;
}
char * sendData = "你好,TCP服务端,我是客户端!\n";
send(sclient, sendData, strlen(sendData), 0);
char recData[255];
int ret = recv(sclient, recData, 255, 0);
if(ret > 0)
{
recData[ret] = 0x00;
printf(recData);
}
closesocket(sclient);
WSACleanup();
return 0;
}
UDP请参考: http://www.cnblogs.com/churi/archive/2013/02/27/2935427.html