实验1
利用Socket,编写一个聊天程序
协议设计
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
AF_INET
指定地址类型
SOCK_STREAM
指定服务类型为流式Socket。
IPPROTO_TCP
指定协议为TCP。
使用一个服务器来监听客户端请求,客户端请求发送信息,服务器收到信息后打印出信息并回复信息到客户端,客户端收到信息也可以再次向服务器发送信息,如此循环;若客户端或者服务器发出了exit
则终止聊天,关闭Socket,结束程序。
Socket编程流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A8ogvnLp-1604834786680)(081508303133673.png)]
实验过程
实验环境为windows下的dev c++编译器。
下面以客户端socket来介绍一下程序执行流程。
winsock初始化
Winsock是Windows下的网络编程接口,它是由Unix下的BSD Socket发展而来,是一个与网络协议无关的编程接口。在windows下有两个主要版本,本次实验采用winsock2。编写socket程序时,需要在头文件处引用
winsock2.h
头文件,且需要链接ws2_32.lib
库文件。
每个Winsock程序必须使用WSAStartup载入合适的Winsock动态链接库,如果载入失败,WSAStartup将返回SOCKET_ERROR,WSAStartup函数定义如下:
int WSAStartup(
WORD wVersionRequested,
LPWSADATA lpWSAData
);
wVersionRequested
指定需要的Winsock版本。你可以使用宏 MAKEWORD(x, y)来指定版本号,例如:WORD sockVersion = MAKEWORD(2,2);
这里x代表主版本,而y代表次版本。lpWSAData
是一个指向WSAData
结构的指针,WSAStartup
会向该结构中填充载入的Winsock动态链接库信息。
我的初始化代码如下:如果初始化失败则退出程序。(失败时返回SOCKET_ERROR(-1))
WORD sockVersion = MAKEWORD(2,2);
WSADATA wsaData;
if(WSAStartup(sockVersion, &wsaData)!=0)
{
return 0;
}
创建socket
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sclient == INVALID_SOCKET)
{
printf("invalid socket!");
return 0;
}
初始化socket
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"); //ip地址
//127.0.0.1是本机地址,主要用于测试,即Localhost.使用回送地址发送数据
//协议软件立即返回之,不进行任何网络传输。
sockaddr_in
结构体的定义在_ip_types.h
文件中可以找到
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
创建连接
使用connect
函数来创建连接,函数定义如下
WINSOCK_API_LINKAGE int WSAAPI connect(
SOCKET s,//套接字
const struct sockaddr *name, //套接字地址
int namelen//套接字长度
);
发送&接收
使用send
和recv
函数来分别实现信息的发送与接收。定义类似于connect函数。
flags 参数一般置0;
WINSOCK_API_LINKAGE int WSAAPI send(SOCKET s,const char *buf,int len,int flags);
WINSOCK_API_LINKAGE int WSAAPI recv(SOCKET s,char *buf,int len,int flags);
关闭socket&终止winsocket
使用closesocket(SOCKET S)
函数关闭套接字,并使用WSACleanup()
函数解除与Socket库的绑定并且释放Socket库所占用的系统资源。
至此客户端的一次发送或者接受信息的流程就完成了,要想多次发送信息就使用循环体即可,当然要想实现同时收发信息,需要使用多线程编程,遗憾的是,我没有成功实现。
服务端的区别在于创建完socket并初始化之后需要使用bind
来绑定一个本地端口,之后使用listen
函数和accept
函数来监听、等待请求,收到请求时accept
会返回一个套接字句柄,用这个套接字实现收发信息,完毕后,一次连接结束,closesocket
,所以循环的使用accept函数接受远程连接即可实现多次收发信息,由于多线程编程掌握的不好,我实现的聊天程序只能是“回合制”的,你一句我一句的形式,这个比较遗憾。
//bind
WINSOCK_API_LINKAGE int WSAAPI bind(SOCKET s,const struct sockaddr *name,int namelen);
//accept
WINSOCK_API_LINKAGE SOCKET WSAAPI accept(SOCKET s,struct sockaddr *addr,int *addrlen);
r *name,int namelen);
//accept
WINSOCK_API_LINKAGE SOCKET WSAAPI accept(SOCKET s,struct sockaddr *addr,int *addrlen);