今天复习了一下计算机网络的知识,就动手写了一下最简单的winsock的程序,还是有不少收获。
程序的注释中有详细的介绍各函数的功能和用法,相信对刚入门的童鞋会有帮助。
具体程序见下,服务器端:
// server.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "Winsock2.h"
//#pragma comment (lib,"ws2_32.lib");
int _tmain(int argc, _TCHAR* argv[])
{
//这个结构被用来存储被WSAStartup函数调用后返回的Windows Sockets数据
WSADATA wsaData;
WORD wVersionRequested;
int err,n;
char buff[512]={0};
//MAKEWORD是将两个byte型合并成一个word型,一个在高8位,一个在低8位
wVersionRequested=MAKEWORD(2,2);
//为了在应用程序当中调用任何一个Winsock API函数,首先第一件事情就是必须通过WSAStartup函数完成对Winsock服务的初始化,因此需要调用WSAStartup函数
//wVersionRequested:一个WORD(双字节)型数值,指定了应用程序需要使用的Winsock规范的最高版本。
//wsaDATA: 指向WSADATA数据结构的指针,用来接收Windows Sockets实现的细节
err=WSAStartup(wVersionRequested,&wsaData);
if(err!=0)
return 0;
//int socket(int domain, int type, int protocol);
//socket函数对应于普通文件的打开操作(类似于fopen函数)。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符,它唯一标识一个socket。
//这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。
//domain: 即协议域,又称为协议族(family)
//type: 指定socket类型
//protocol:指定协议
SOCKET sock=socket(AF_INET,SOCK_STREAM,0);
//sin_family指代协议族,在socket编程中只能是AF_INET
//sin_port存储端口号(使用网络字节顺序)
//sin_addr存储IP地址,使用in_addr这个数据结构
//htonl将一个32位数从主机字节顺序转换成网络字节顺序
//htons将机器上的整数转换成“网络字节序”,网络字节序是 big-endian,也就是整数的高位字节存放在内存的低地址处。
//而我们常用的 x86 CPU (intel, AMD) 电脑是 little-endian,也就是整数的低位字节放在内存的低字节处。
SOCKADDR_IN address;
address.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
address.sin_family=AF_INET;
address.sin_port=htons(6000);
//int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//bind()函数把一个地址族中的特定地址赋给socket
//sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket
//addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址
//addrlen:对应的是地址的长度
bind(sock,(SOCKADDR*)&address,sizeof(SOCKADDR));
//如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。
//listen函数的第一个参数即为要监听的socket描述字
//第二个参数为相应socket可以排队的最大连接个数
//socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。
listen(sock,5);
printf("Waiting for client's request......\n");
SOCKADDR_IN addrClient;
int len=sizeof(SOCKADDR);
while(1)
{
//accept函数的第一个参数为服务器的socket描述字
//第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址
//第三个参数为协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接
SOCKET conn=accept(sock,(SOCKADDR*)&addrClient,&len);
n = recv(conn,buff,512,0);
buff[n]='\0';
printf("recv msg from client: %s\n", buff);
if(strcmp(buff,"close connect")==0)
{
closesocket(conn);
break;
}
}
closesocket(sock);
return 0;
}
客户端:
// client.cpp : 定义控制台应用程序的入口点。
//
//#pragma comment "ws2_32.lib"
#include "stdafx.h"
#include "Winsock2.h"
int _tmain(int argc, _TCHAR* argv[])
{
WSADATA wsaData;
WORD wVersionRequested;
int err,n;
char buff[512];
wVersionRequested=MAKEWORD(2,2);
err=WSAStartup(wVersionRequested,&wsaData);
if(err!=0)
return 0;
SOCKET clientSock=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN address;
address.sin_addr.S_un.S_addr=inet_addr("162.105.247.166");
address.sin_family=AF_INET;
address.sin_port=htons(6000);
connect(clientSock,(SOCKADDR*)&address,sizeof(address));
char recvBuf[100];
send(clientSock,"close connect",strlen("close connect"),0);
closesocket(clientSock);
WSACleanup();
return 0;
}
大家单步调试会发现,如果client端进程没有开启,那么server端就会一直停留在accept()函数处,直到client端运行到connect()函数,二者之间才建立连接。
把程序改改,然后让朋友们都装个VS,把client的代码发给他们,咱以后就在console下聊天了,直接把企鹅卸载了,哈哈哈~~~