前面已经介绍了简单TCP套接字编程:https://blog.csdn.net/weixin_42109012/article/details/105308403
但是只是实现了一对一的简单通讯,这是由于函数的阻塞机制导致的,同一时间只能一个客户端访问。
一、SOKECT操作模式
1、 SOKECT操作模式都可以看做
(1)阻塞:
默认模式
针对函数、针对线程
执行到某函数,停在那,函数有结果或者接收到数据后才执行下一条语句
当前线程会被挂起
阻塞式函数(可设定为阻塞的函数)
阻塞函数(总是阻塞)
(2)非阻塞:
针对函数和线程
不管成功与否,函数立即返回,执行下一条语句
可能还没收到数据
可能过一会才会有结果
需要过一定时间再来查看结果或接收数据。
(3)同步:
针对事务,针对客户端与服务器端的配合,特别针对客户端。
一个事务,一端发出请求后,必须等待另一边返回结果后才会继续下一个事务。
(4)异步:
针对事务,针对客户端与服务器端的配合
客户端和服务器端各干各的
客户端发了个请求,不会等待服务器端响应,接着干别的了。
服务器端处理完客户端请求后,通知客户端接收或处理结果。
一般需要设置回调函数或构建消息机制
(5 )操作模式关系
阻塞一定是同步
同步不一定阻塞
异步一定是非阻塞
2、Windows操作系统提供了以下几种模型。
选择(Select)
异步选择(WSAAsyncSelect)
事件选择(WSAEventSelect)
重叠I/O(Overlapped I/O)
完成端口(Completion Port)
二、阻塞模式多线程实现多客户端
1、 阻塞式函数
(1)一定阻塞
Socket、bind、listen、select
(2)不一定阻塞(可以设为非阻塞)
1、Accept
等客户端的连接(1个连接成功,返回)
2、Connect
默认超时时间:大概75秒
不断尝试连接服务器端(一旦连接成功,超时退出)
3、Send/sendto
发送缓冲区大小,1.1默认值8k,2.2为64k
发送缓冲区是否填满,(没满,完成写缓存,返回)
4、Recv/recvfrom
接收缓冲区数据是否为空(1个字节以上返回,返回同时清除缓冲区中对应数据)
2、多线程服务端程序设计
(1)线程概念
每个线程独立运行,互不干扰,一个线程阻塞,不影响其它线程执行
(2)函数介绍
_BegingThread(基本C、c++)
库文件:#include <process.h>
创建函数:
uintptr_t _beginthread(
void( *start_address )( void * ), //线程函数
unsigned stack_size, //栈大小,0
void *arglist //参数,NULL
);
线程函数:void threadproc(void * param)
线程结束:_endthread();
_beginthreadex(c++、vc++)
创建函数:
unsigned long _beginthreadex(
void *security, //安全属性,NULL
unsigned stack_size, //堆栈的大小,0
unsigned ( __stdcall *start_address )( void * ), //线程函数
void *arglist, //参数,NULL
unsigned initflag, //0立即运行
unsigned *thrdaddr ); //线程ID
线内结束: _endthreadex
其他同_beginthread
三、网络编程——阻塞TCP多线程多客户端
使用的是阻塞模式下多线程实现多客户端
1、服务端——server.cpp
/*****************************************************************************
* @author: ljf *
* @date : 2020/03/17 *
* @file : Server.cpp *
* @brief : 基于TCP协议通信——服务端 *
*----------------------------------------------------------------------------*
* Change History *
*----------------------------------------------------------------------------*
* Date | Version | Author | Description *
*----------------------------------------------------------------------------*
* 2020/03/17 | 1.0 | ljf | 创建并简单实现 *
*----------------------------------------------------------------------------*
* 2020/03/31 | 2.0 | ljf | 实现多客户端同时访问 *
*****************************************************************************/
#include <stdio.h>
#include <string.h>
#include <winsock.h>
#include <process.h>
#pragma comment(lib, "wsock32.lib") //隐式加载
#define BUFF_SIZE 1024 //宏定义缓冲区大小
/*****************************************************************************
* @data : 2020/3/31 *
* @brief : 多线程实现信息交互 *
* @input : *
* *p : 客户端套接字 *
* @output: *
* none : none *
*****************************************************************************/
void my_thread(void* p) {
char RECV_BUFF[BUFF_SIZE]; //接收数据
char reply[] = "已收到";
SOCKET client_socket = *(SOCKET*)p;
sockaddr_in client_addr;
int client_addr_len = sizeof(client_addr);
//获取客户端信息
getpeername(client_socket, (sockaddr*)&client_addr, &client_addr_len);
int client_port = ntohs(client_addr.sin_port);
char client_ip[20];
strcpy_s(client_ip, inet_ntoa(client_addr.sin_addr)); //将转换后的IP地址从缓冲区复制出来
//信息交互
while (1) {
//清空缓冲区
memset(RECV_BUFF, 0, BUFF_SIZE);
//信息是否接受成功
if (0 < recv(client_socket, RECV_BUFF, BUFF_SIZE, 0)) {
printf("[Recv %s:%d]: %s\n", client_ip, client_port, RECV_BUFF);
send(client_socket, reply, strlen(reply), 0);
}
//排除了0,负数情况下客户端已关闭
else {
break;
}
}
printf("[%s:%d Disconnected]\n", client_ip, client_port);
closesocket(client_socket);
_endthread();
}
int main() {
//初始化套接字
WSADATA wsadata;
if (WSAStartup(MAKEWORD(1, 1), &wsadata) != 0) { //版本1
printf("WSAStartup error ---- Error Code: %d", WSAGetLastError());
WSACleanup();
return -1;
}
//创建监听套接字
SOCKET server_socket;
server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == server_socket) {
printf("create socket error ---- Error Code: %d", WSAGetLastError());
WSACleanup();
return -2;
}
//初始化监听信息
sockaddr_in server_addr; //服务器地址信息
int server_addr_len = sizeof(server_addr);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(5678);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //inet_addr("127.0.0.1")
if (SOCKET_ERROR == bind(server_socket, (SOCKADDR*)&server_addr, server_addr_len)) {
printf("socket bind error ---- Error Code: %d", WSAGetLastError());
WSACleanup();
return -3;
}
//监听端口
if (SOCKET_ERROR == listen(server_socket, 5)) {
printf("socket listen error —— Error Code: %d", WSAGetLastError());
WSACleanup();
return -4;
}
printf("Listening...\n");
sockaddr_in client_addr; //客户端地址,等待连接
int client_addr_len = sizeof(client_addr); //客户端地址大小
SOCKET client_socket; //客户端套接字
//TCP阻塞accept,多线程接收信息
while (1) {
client_socket = accept(server_socket, (SOCKADDR*)&client_addr, &client_addr_len);
printf("[%s:%d Connected]\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
_beginthread(my_thread, 0, &client_socket);
}
closesocket(server_socket); //关闭监听套接字
WSACleanup(); //释放套接字资源
system("pause"); //等待
return 0;
}
2、客户端——client.cpp
/*****************************************************************************
* @author : ljf *
* @date : 2020/03/17 *
* @file : Client.cpp *
* @brief : 基于TCP协议通信——客户端 *
*----------------------------------------------------------------------------*
* Change History *
*----------------------------------------------------------------------------*
* Date | Version | Author | Description *
*----------------------------------------------------------------------------*
* 2020/03/17 | 1.0 | ljf | 创建并简单实现 *
*----------------------------------------------------------------------------*
* 2020/03/31 | 2.0 | ljf | 实现自己编辑发送数据 *
*****************************************************************************/
#include <stdio.h>
#include <string.h>
#include <winsock.h>
#pragma comment(lib, "wsock32.lib") //隐式加载
#define BUFF_SIZE 1024 //宏定义缓冲区大小
int main() {
int HOST_PORT = 5678; //服务器端口
char HOST_IP[20] = "127.0.0.1"; //服务器IP
char RECV_BUFF[BUFF_SIZE]; //缓冲区
char SEND_BUFF[BUFF_SIZE];
//初始化套接字
WSADATA wsadata;
if (0 != WSAStartup(MAKEWORD(1, 1), &wsadata)) { //版本1
printf("WSAStartup error —— Error Code: %d", WSAGetLastError());
WSACleanup();
return -1;
}
//创建客户端套接字
SOCKET client_socket;
client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == client_socket) {
printf("create socket error —— Error Code: %d", WSAGetLastError());
WSACleanup();
return -2;
}
//初始服务器地址信息
sockaddr_in server_socket;
server_socket.sin_family = AF_INET;
server_socket.sin_port = htons(HOST_PORT);
server_socket.sin_addr.s_addr = inet_addr(HOST_IP);
if (SOCKET_ERROR == connect(client_socket, (SOCKADDR*)&server_socket, sizeof(server_socket))) {
printf("[%s:%d Connect failed —— Error Code: %d]\n", inet_ntoa(server_socket.sin_addr), ntohs(server_socket.sin_port), WSAGetLastError());
WSACleanup();
return -3;
}
else {
printf("[%s:%d Connect success]\n", inet_ntoa(server_socket.sin_addr), ntohs(server_socket.sin_port));
}
//信息交互
while (1) {
//清空缓冲区
memset(SEND_BUFF, 0, BUFF_SIZE);
memset(RECV_BUFF, 0, BUFF_SIZE);
printf("[Send]: ");
//读取一行字符串,回车结束,回车会被读入
fgets(SEND_BUFF, BUFF_SIZE, stdin);
//处理末尾的回车
SEND_BUFF[strlen(SEND_BUFF) - 1] = '\0';
if (strlen(SEND_BUFF) != 0) {
//收发数据
send(client_socket, SEND_BUFF, strlen(SEND_BUFF), 0);
recv(client_socket, RECV_BUFF, BUFF_SIZE, 0);
printf("[Recv]: %s\n", RECV_BUFF);
}
}
closesocket(client_socket); //关闭客户端套接字
WSACleanup(); //释放套接字资源
system("pause"); //等待
return 0;
}
四、结果
五、总结
1、问题
客户端同时连接
发送数据太大处理