文章目录
- 前言
- 一、Socket的API函数
- 二、服务端建立Socket步骤
- 总结
前言
C++打造局域网聊天室第七课: Socket编程初步2
一、Socket的API函数
接着上一课的内容,我们在chartroom.cpp中找到如下位置
插入断点,运行
运行到断点处后,按F11可以进入该函数内部
可以看到其调用了函数WSAStartup函数,进行了Socket库的初始化操作。
二、服务端建立Socket步骤
当用户点击开启服务器按钮时进行服务器端的开启,利用MFC消息映射机制建立该按钮的单击后需要的执行功能函数,同样会自动生成头文件中的函数声明,源文件中的函数实现框架,具体步骤见MFC消息映射机制
建立一个监听的Socket,在chartroomDlg.h头文件中声明变量。
然后在chartroomDlg.cpp源文件中构造函数处进行初始化,INVALID_SOCKET为一个宏,被定义为-1,表示一个不存在的Socket值。
之后在OnBnClickedButton3()函数实现中进行服务器端建立Socket函数实现。具体代码见如下代码块,各行代码的意义见注释。
void CchartroomDlg::OnBnClickedButton3()
{// TODO: 在此添加控件通知处理程序代码//新建:调用socket函数新建Socket,第一个参数为协议族,利用IPV4就用该参数;第二个和第三个参数为建立流式套接字;函数成功会返回一个Socket,//用作监听的Socket,用于监听某一个端口。m_ListenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (m_ListenSock == INVALID_SOCKET) // 如果新建失败{AfxMessageBox(_T("新建SOCKET失败!"));return;}// 绑定:将新建的socket绑定在本机新建的一个IP和端口上,是在用户界面本地监听端口处动态获取的UINT uPort = GetDlgItemInt(IDC_EDIT7); //GetDlgItemText函数取得字符串,GetDlgItemInt函数取得整数if (uPort <= 0 || uPort > 65535) // 对端口值进行判断{AfxMessageBox(_T("请输入合适的端口:1-65535"));goto __Error_End;}sockaddr_in service;service.sin_family = AF_INET; //与新建socket第一个参数的值一样service.sin_addr.s_addr = INADDR_ANY; //获取本机所有可能得到的IP,一般有几块网卡就有几个IP//cout << service.sin_addr.s_addr;service.sin_port = htons(uPort); //将端口传递给sin_port成员,htons为字节顺序转换函数,利用该函数是因为常用的CUP字节顺序与网络字节顺序相反// 例如地址0x12345678,host:0x78 0x56 0x34 0x12; net:0x12 0x34 0x56 0x78。h为host(主机),n为network。htons即为将主机的字节顺序转化为net顺序if (bind(m_ListenSock, (sockaddr*)&service, sizeof(sockaddr_in)) == SOCKET_ERROR)// 第一个参数为需要绑定的socket;第二个参数为一个sockaddr_in结构,需要进行一个强制类型转换;第三个参数为该结构的大小{AfxMessageBox(_T("绑定端口失败!"));goto __Error_End;}// 监听:第一个参数为传递的socket,第二个参数为监听序列中允许同时存在的客户端最大数目if (listen(m_ListenSock, 5) == SOCKET_ERROR){AfxMessageBox(_T("监听失败!"));goto __Error_End;}// 接受客户端的连接:sockaddr_in clientAddr;int iLen = sizeof(sockaddr_in);// 第一个参数为socket,第二个参数为一个sockaddr_in结构,需要进行一个强制类型转换,第三个参数为该结构的大小的地址。// 返回的socket为服务端与接受的客户端通信的socket,即利用accsocket进行与其他客户端的通信,m_ListenSock只是在服务器端进行监听SOCKET accSock = accept(m_ListenSock, (struct sockaddr *)&clientAddr , &iLen);if (accSock == INVALID_SOCKET){AfxMessageBox(_T("接受客户端失败"));goto __Error_End;}//其他操作// 出现错误则关闭监听,退出函数
__Error_End:closesocket(m_ListenSock);
}
但是目前来说,点击开启服务器会使得程序崩溃,卡死。只能通过终止调试按钮停止程序。
这就涉及到了另外的知识点:阻塞与非阻塞。
阻塞是指干不完活不回来,在那里堵住了,这样的话调用阻塞函数的线程被挂起。该程序阻塞在了accept函数那里,使得主线程被挂起。比如WinSocket API中的accept、send等函数就是阻塞的。阻塞类似于C++中的cin函数,如果用户不输入的话程序不会向下执行,会卡在那里。
非阻塞是指你先干活,我先不干这个活,你干完了我再来
阻塞特点:使用简单但是效率低
非阻塞特点:使用复杂,一些socket API函数执行后总是马上返回WSAEWOULDBLOCK错误,表明请求的操作没有成功完成,如果循环检查的话知道有信息到达,效率肯定会降低,也不易于新手使用
可以使用Windows提供的各种异步I/O模型进行解决,在下节课中介绍。
总结
C++打造局域网聊天室第七课: Socket编程初步2