Java程序通过封装Windows API,提供给用户相关Socket类,实现一个简单的Web服务器较好理解。用c语言直接调用Windows API 中的winsocket相关函数实现简单的Web服务器,相对来说就复杂些了。通过了解这个实现过程,也能更好的理解Java封装后,实现开发的快捷和易于学习。
下面是这个小程序的C语言源代码:
#include <stdio.h>
//Winsock2.h头文件,里面有多种函数可以帮助使用Winsock.
//Winsock是Windows下网络编程的规范,该规范是Windows下得到广泛应用的、开放的、支持多种协议的网络编程接口.
#include <Winsock2.h>
//连接Ws2_32.lib这个库,ws2_32.lib是winsock2的库文件.
#pragma comment(lib,"ws2_32.lib")
main()
{
//typedef unsigned short WORD; WORD是无符号短整型的别名.
WORD wVersionRequested;
//WSA(Windows Sockets Asynchronous,Windows异步套接字),不少标识符用它做前缀.
WSADATA wsaData;
int err;
//程序使用1.1版本的Socket,宏MAKEWORD( 1, 1 )的值是257的短整数,占两个字节,每个字节都是1.
wVersionRequested = MAKEWORD( 1, 1 );
/*
(1)WSAStartup()负责WSA(Windows Sockets Asynchronous,Windows异步套接字)的启动。当应用程序调用WSAStartup函数时,
操作系统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。
以后应用程序就可以调用所请求的Socket库中的其它Socket函数(Windows Sockets API函数)了。
该函数执行成功后返回0。
(2)WSA是windows提供的一个与网络协议无关的编程接口,也就是说不仅支持TCP/IP协议族。
(3) WSADATA是结构体类型,用来保存函数WSAStartup执行后返回的Windows Sockets初始化信息
*/
err = WSAStartup( wVersionRequested, &wsaData );
//返回值为0,WSAStartup()执行成功
if ( err != 0 ) {
return;
}
//宏 LOBYTE和 HIBYTE分别检查 版本号的低8位和高8位是否是1.
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {
//有一个不是1,注销 WSA的启动,并释放资源
WSACleanup();
return;
}
/*
socket(AF_INET,SOCK_STREAM,0)函数的三个参数分别是:
(1)AF,address family,用来指定使用的地址族(也可理解为协议族,协议族不同,地址形式也不同)。AF_INET的值是2,表示使用IPV4的地址形式
(2)通信类型。SOCK_STREAM的值是1,指定使用 字节流的套接字 通信类型。
(3)协议类型。IPV4中仅有一种协议类型,只能指定为0.
如果函数调用成功,会返回一个标识这个套接字的文件描述符(是个整数,每次连接都不同),失败的时候返回-1。
*/
SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,0);
//设置socket 地址属性
SOCKADDR_IN addrSrv;
//htonl()函数将一个32位数从主机字节顺序转换成无符号长整型的网络字节顺序(host to net long) .
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(9000);
//bind(SOCKET s,const struct sockaddr *name,int namelen);
bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
//listen(SOCKET s,int backlog);backlog指监听队列最大监听连接数
listen(sockSrv,5);
//addrClient用于存放客户端socket 的地址属性
SOCKADDR_IN addrClient;
//SOCKADDR结构体,用于存储参与(IP)Windows/linux套接字通信的计算机上的一个internet协议(IP)地址,长度是16字节
int len=sizeof(SOCKADDR);
while(1)
{
//accept(SOCKET s,struct sockaddr *addr,int *addrlen)获取客户端的socket
SOCKET sockConn=accept(sockSrv,(SOCKADDR*)&addrClient,&len);
char sendBuf[500];
//sprintf()函数把目标字符串装到 sendBuf数组中,第一个参数是指针类型。
sprintf(sendBuf,"HTTP/1.1 200 ok\r\nContent-Type:text/html\r\n\r\nWelcome %s to here!",inet_ntoa(addrClient.sin_addr));
//strlen(sendBuf)+1,加1是让发送缓冲区大于要发送的字节数,第四个参数是传送方式(比如要传输带外数据),一般设置0即可。
send(sockConn,sendBuf,strlen(sendBuf)+1,0);
char recvBuf[1500];//网络最大传输单元1500字节,在这两就设定这个大小就可以了
recv(sockConn,recvBuf,1500,0);
printf("%s\n",recvBuf);
closesocket(sockConn);
}
}
代码使用 dev-C++5.11编辑和运行。在运行时注意添加一项编译配置, 在连接器命令行中添加“-lwsock32”命令。"工具"-"编译配置",如下图:
代码运行后,在浏览器中的访问如下图:
在服务器端看到浏览器的请求头如下图: