一, 寻址方式(IP地址表示)
在Windows Socket API 中,用户可以使用TPC/IP 地址家族中统一的套接字地址结构去处理 TCP/IP 中的寻址问题, 相关结构如下:
struct sockaddr_in{
short sin_family; //指定地址家族
unsigned sin_port; //端口号码
struct in_addr sin_addr; //IP地址
char sin_zero[8] //备用,常作0
}
备注:因为是涉及到TCP/IP 相关编程,因此 sin_family 必须指定为 AF_INET.
结构变量sin_addr表示32位的IP地址, 结构定义如下:
struct in_addr
{
union{
struct{
unsigned char s_b1, s_b2, s_b3, s_b4; //用4个u_char字符描述IP地址
}S_un_b;
struct{
unsigned short s_w1, s_w2; //用2个u_short类型描述IP地址
}S_un_w;
struct{
unsigned long s_addr; //用1个u_long类型描述IP地址
}S_un;
}
}
通常情况下用户会使用1个u_long类型表示IP地址,如要描述IP地址"127.0.0.1" 的代码如下:
sockaddr_in addr
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
二, 字节顺序
在Socket套接字编程中,传输数据的排列顺序分为: 网络字节顺序 及主机字节顺序 两种.
从数据角度来看,
网络字节顺序 会将数据中重要的字节先进行存储,
主机字节顺序 会将数据中不重要的字节先进行存储.
通常情况下,
若用户需要通过网络发送数据,则需要将数据转换成网络字节顺序排列,
若用户需要将从网络接收到的数据存储到本地主机上,则需要将数据转换成主机字节顺序排列.
以下几个函数可以进行网络字节顺序及主机字节顺序之间的转换:
u_short htons(u_short hostshort); // 将一个u_short类型的IP地址从主机字节顺序转换到网络字节顺序
u_short ntohs(u_short netshort); // 将一个u_short类型的IP地址从网络字节顺序转换到主机字节顺序
u_long htonl(u_long hostlong); // 将一个u_long类型的IP地址从主机字节顺序转换到网络字节顺序
u_long ntohl(u_long netlong); // 将一个u_long类型的IP地址从网络字节顺序转换到主机字节顺序
unsigned long inet_addr(const char FAR* cp); // 将一个字符串转换到以网络字节顺序排列的IP地址
char FAR* inet_ntoa(struct in_addr in); // 将一个网络字节顺序排列的IP地址转换成一个字符串IP
三, Socket套接字 相关函数
1.加载套接字库,创建并初始化套接字(socket())
socket()是创建套接字的函数,原型如下:
SOCKET socket(
int af, //指定套接字使用的地址格式,TCP/IP只能设定为AF_INET
int type, //套接字类型
int protocol //如果type已经指定套接字类型为TCP或UDP,则该参数设置为0
);
该函数执行成功则新创建的套接字句柄,失败则返回INVALID_SOCKET.
其中type的取值有以下三种情况:
SOCK_STREAM 创建流式套接字(适用TCP协议)
SOCK_CGRAM 创建数据报套接字(适用UDP协议)
SOCK_RAW 创建原始套接字
例如,需要创建流式套接字的句柄,则可以使用以下代码:
SOCKET s_server = socket(AF_INET, SOCK_STREAM, 0);
SOCKET s_client = socket(AF_INET, SOCK_STREAM, 0);
客户端创建socket用于通讯.
2.服务器绑定套接字到一个IP地址和一个端口上(bind())
对于服务器而言,创建套接字成功后,需要将套接字与地址信息进行关联,这时需要用到bind(),
bind()的原型如下:
int bind(
SOCKET s, // 套接字句柄
const struct SOCKADDR FAR* name, // 地址结构内容
int namelen // 地址结构大小
);
若函数执行成功则返回0,失败则返回-1.
例如,若服务器需要将套接字绑定到本地地址,代码则如下:
SOCKET s_server = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in address; //声明套接地址结构变量
address.sin_family = AF_INET; //指定地址家族为TPC/IP
address.sin_port = htons(8080); //指定端口号8080
address.sin_addr.S_un.S_addr = INADDR_ANY; //表示服务器能够接收任何地址计算机发来的请求
::bind(s_server, (SOCKADDR*) &address, sizeof(addr)); //绑定套接字与地址结构
又例如,客户端在向服务器发送连接请求前,则需要按以下代码初始化地址结构(此时的地址结构需要填写相关服务器地址以及服务器监听的端口):
SOCKET s_client = socket(AF_INET, SOCK_STREAM, 0);
sockadr_in address;
address.sin_family = AF_INET;
address.sin_port = htons(8080); //通讯端口对应服务器的端口8080
address.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //指定服务器地址
3.服务器将套接字设置为监听模式等待连接请求(listen())
当服务器程序将套接字句柄绑定到地址结构成功后,则接着调用listen()实现监听端口模式.
listen()原型如下:
int listen(
SOCKET s, //实现监听功能的套接字句柄,实际为服务器bind()调用的套接字s相关
int backlog //指定监听的最大连接数量
);
若函数执行成功则返回0,失败则返回-1.
以下代码则为服务器实现监听功能:
listen(s_server, 5); //在服务器套接字上进行监听,并指定最大监听数为5
4.客户端:客户端在初始化完成后,会向服务器发送连接请求(connect())
服务器:请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept())
如果客户端需要连接服务器,则需要调用函数connect().
函数原型如下:
connect(
SOCKET s, //客户端自己创建的套接字s
const struct SOCKADDR FAR* name,//即将连接的服务器地址结构的指针
int namelen //地址结构的长度
);
以下代码则实现客户端向服务器发送连接请求:
connect(s_client, (SOCKADDR*)&addr, sizeof(addr));
如果服务器接收到客户发送的连接请求(connect),若要接受该请求调用accept()函数.
accetp()函数原型如下:
SOCKET accetp(
SOCKET s, //服务器套接字句柄
struct sockaddr FAR* addr, //获取客户端的地址信息
int FAR* addrlen //地址长度
);
若函数调用成功,则返回一个对应于客户端连接请求的套接字标识,失败则返回-1.
例如,以下代码则服务器实现了接收客户端的发送的请求并建立连接:
int len = sizeof(address);
SOCKET s_connected = accept(s_server, (SOCKADDR*) &address, &len);
5.用返回的套接字和客户端进行通信(send()/recv());
在服务器与客户端建立连接后,可以调用函数send()/recv()进行数据的发送及接收,
send()/recv()的函数原型如下:
int send(
SOCKET s,
const char FAR* buf, //指向数据缓冲区的指针变量
int len, //缓冲区数据的大小,即需要发送的数据大小
int flags //此参数通常设置为0
);
int recv(
SOCKET s,
char FAR* buf //指定数据缓冲区的指针变量
int len, //缓冲区数据的大小,即已经接收的数据大小
int flags //同send()
);
备注:当服务器使用send()发送数据时,参数s应是listen()函数返回的对应客户端的套接字标识;
如果是客户端使用send()发送数据时,参数s则是客户端自己创建的套接字句柄.
服务器的发送及接收数据部分代码如下:
char buf[1024];
send(s_connected, buf, sizeof(buf), 0); //向客户端发送数据
recv(s_connected, buf, sizeof(buf), 0); //接收客户端的数据
客户端的发送及接收数据部分代码如下:
char buf[1024]
send(s_client, buf, sizeof(buf), 0); //向服务器发送数据
recv(s_client, buf, sizeof(buf), 0); //接收服务器的数据
6.返回,等待另一连接请求;
7.关闭套接字,关闭加载的套接字库(closesocket())。
当套接字通讯完毕或程序退出时,用户需要调用函数closesocket()关闭套接字句柄.
closesocket()函数原型如下:
int closesocket(
SOCKET s //需要关闭的套接字句柄
);
以下代码分别为服务器及客户端需要关闭套接字句柄的代码:
服务器关闭套接字句柄:
closesocket(s_server);
colsesocket(s_connected);
服务器关闭套接字句柄:
closesocket(s_client);