socket 网络编程(windows版)
c++多线程实现网络中的进程tcp/ip通信
进程间通信(IPC)有很多种方式,但可以总结为下面4类:
- 消息传递(管道、FIFO、消息队列)
- 同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)
- 共享内存(匿名的和具名的)
- 远程过程调用(Solaris门和Sun RPC)
我们要讨论的是网络中进程之间如何通信?首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。其实TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。
客户机和服务器的进程实现网络通信的流程
服务器:
- 创建一个socket套接字,
- 绑定监听端口;使用bind函数将客户端需要访问的ip地址和端口绑定到socket中
- 设置监听队列;使用listen函数监听这个socket
- 循环等待客户端的连接请求;使用accept函数和多线程接收所有的客户端发送过来的连接请求
- 使用receive函数接收客户端发来的数据
- 使用send函数由服务器发送数据或者资源给相应的客户机
- close函数关闭服务端的socket
客户机:
- 创建一个socket套接字,但这个套接字是用来储存目的IP和目的端口号的(即该客户机想要访问的某一台服务器的某一个IP地址和端口号)
- 使用connect函数与客户端进行连接操作,
- close函数关闭客户端的socket
下面贴cpp代码
代码使用指南:
- client.cpp端要求输入两个参数,第一个参数为目的ip地址,一般为127.0.0.1,第二个为端口号,固定为61717,因为server端被监听的端口号就是61717
- client.cpp文件有一堆是判断输入是否合法的if else分支,可以自动略过
- server端使用一个数组和accept方法实现等待队列,用来接收各个客户端的连接申请,同时利用多线程,处理等待队列中的客户端请求,
- 关于如何实现模拟多个客户端并发访问一个服务器,只需要在visual.studio中重复运行client.cpp文件即可(注意第二次开始运行时应该使用"添加新实例",否则会报错)
client.cpp
#include <winsock2.h>
#include <stdio.h>
#include <stdlib.h>
#include <WS2tcpip.h>
#include <iostream>
#include<string>
using namespace std;
#pragma comment(lib , "ws2_32.lib")
// defines the buffer size
#define BUFSIZE 4096
// judge whether it is a number or not
bool AllisNum(string str)
{
for (int i = 0; i < str.size(); i++)
{
int tmp = (int)str[i];
if (tmp >= 48 && tmp <= 57)
{
continue;
}
else
{
return false;
}
}
return true;
}
int main(int argc, char* argv[])
{
WSADATA wsd;
SOCKET sClient;
char Buffer[BUFSIZE];
int ret;
struct sockaddr_in server;
unsigned short port;
struct hostent* host = NULL;
// judge whether two args are legal
if (argc < 3) {
printf("Usage:%s IP Port \n", argv[0]);
return -1;
}
int portAddr;
if (AllisNum(argv[2]))
{
portAddr = atoi(argv[2]);
if (portAddr > 65535 || portAddr < 61000 || portAddr < 0)
{
cout << "Invalid command line argument detected: "<< argv[2] << endl;
cout << "Please check your values and press any key to end the program!" << endl;
}
}
else
{
cout << "Invalid command line argument detected: "<< argv[2] << endl;
cout << "Please check your values and press any key to end the program!" << endl;
}
// load Winsock DLL
if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0) {
printf("Winsock 初始化失败!\n");
}
// create socket
sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sClient == INVALID_SOCKET) {
printf("socket() 失败: %d\n", WSAGetLastError());
}
// bind server listenning addr and port number
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));
//server.sin_addr.s_addr = inet_addr(argv[1]);
// this is for the conversion from decimal to binary
struct in_addr inp = {};
string ipAddr = argv[1];
if (ipAddr == "localhost")
inet_pton(AF_INET, "127.0.0.1", &inp);
else
return 3;
server.sin_addr.s_addr = inp.S_un.S_addr;// could also use inet_pton ...
// build connection with the server
if (connect(sClient, (struct sockaddr*)&server, sizeof(server)) == SOCKET_ERROR)
{
printf("Failed to connect to the server at <%s> on <%d>\n"
"Please check your values and press any key to end program!", argv[1], atoi(argv[2]));
}
else
{
// receive and send messages
for (;;) {
printf("%s", "Please enter a message:");
// read data from the standard input function
gets_s(Buffer, BUFSIZE);
// send messages to the server
ret = send(sClient, Buffer, strlen(Buffer), 0);
if (ret == 0) {
break;
}
else if (ret == SOCKET_ERROR) {
printf("send() 失败: %d\n", WSAGetLastError());
break;
}
// receive data from the server
ret = recv(sClient, Buffer, BUFSIZE, 0);
if (ret == 0) {
break;
}
else if (ret == SOCKET_ERROR) {
printf("recv() 失败: %d\n", WSAGetLastError());
break;
}
Buffer[ret] = '\0';
printf("Received: \t%s\n", Buffer);
}
}
closesocket(sClient);// close the socket
WSACleanup(); //clean
system("pause");
return 0;
}
server.cpp
#include <stdio.h>
#include <winsock2.h>
#include <WS2tcpip.h>
#include <iostream>
#include<fstream>
#include <sstream>
#include<string>
#include <windows.h>
#define MAXCLIENTNUM 10
using namespace std;
#pragma comment(lib,"ws2_32.lib")
typedef struct ClientInf
{
SOCKET client_socket;
}ClientInf;
string host_names[4];
int counter = 0;
DWORD WINAPI Thread(LPVOID lpParameter)
{
while (true)// loop
{
// receive data
ClientInf client_inf = *(ClientInf*)lpParameter;
char rev_data[255] = {};
int ret = recv(client_inf.client_socket, rev_data, 255, 0);
// resolve and handle data
char host[NI_MAXHOST]; // Client's remote name
char service[NI_MAXSERV]; // service(i.e.port) the client is connect on
if (ret > 1)
{
ofstream fout("server.log", ios::app);
rev_data[ret] = 0x00;
cout << rev_data << endl;
fout << rev_data << endl;
fout.close();
}
// server informs the client that he receives data
const char* sendData = "$message has been received by the server\r\n";
int whetherSent = send(client_inf.client_socket, sendData, (int)strlen(sendData), 0);
cout << whetherSent << endl;
// detect disconnection
if (whetherSent == SOCKET_ERROR )
{
ofstream fout("server.log", ios::app);
fout<< "one client named "<< host_names[counter] << " disconected" << endl;
fout.close();
return 0;
}
}
return 0;
}
int main(int argc, char* argv[])
{
//init WSA
WORD socketVersion = MAKEWORD(2, 2);//version of winsocket2.2
WSADATA wsaData;
if (WSAStartup(socketVersion, &wsaData) != 0)
{
return 0;
}
// create the socket of server
SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (slisten == INVALID_SOCKET)
{
printf("socket error !");
return 2;
}
// bind ip addr and port number to the socket
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(61717);
sin.sin_addr.s_addr = INADDR_ANY;
// if binding failed, throw error
if (bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)//绑定
{
printf("bind error!");
}
// if listening sth wrong, throw error
if (listen(slisten, 5) == SOCKET_ERROR)
{
return 3;
}
ClientInf client_inf_vector[MAXCLIENTNUM];// an array recieves all clients info
HANDLE thread_handle_vector[MAXCLIENTNUM];// an array stores all threads
int client_num = 0;// the number of clients connected
// this is the main thread that accepts all connections from clients
while (true)
{
printf("waiting for connection request...\n");
sockaddr_in remoteClient;
int nAddrlen = sizeof(remoteClient);
printf("current num of clients:%d\r\n", client_num);
SOCKET c_socket = accept(slisten, (SOCKADDR*)&remoteClient, &nAddrlen);// block and wait
if (client_num < MAXCLIENTNUM)
{
if (c_socket == INVALID_SOCKET)
{
printf("accept error!");
continue;
}
client_inf_vector[client_num].client_socket = c_socket;
char sendBuf[255] = { '\0' };
char host[NI_MAXHOST]; // Client's remote name
char service[NI_MAXSERV]; // service(i.e.port) the client is connect on
ZeroMemory(host, NI_MAXHOST);
// same as memset ( host,0,NI_MAXHOST);
ZeroMemory(service, NI_MAXSERV); // NI_MAXSERV indicates the length of the array need filling with 0
ofstream fout("server.log", ios::app);
if (getnameinfo((sockaddr*)&remoteClient, sizeof(remoteClient), host, NI_MAXHOST, service, NI_MAXSERV, 0) == 0)
{
counter = client_num;
host_names[counter] = host;
fout << host << " connected on port of client " << service << endl;
cout << host << " connected on port of client " << service << endl;
}
else
{
inet_ntop(AF_INET, (void*)&remoteClient.sin_addr, sendBuf, sizeof(sendBuf));
fout << host << " connected on port of client " << service << endl;
cout << host << " connected on port of client " << ntohs(remoteClient.sin_port) << endl;
}
fout.close();
thread_handle_vector[client_num] = CreateThread(
NULL,
0,
Thread,
&(client_inf_vector[client_num]),
0,
NULL
);
client_num++;
}
else
{
printf("connection clients are up to the upper limit:%d\r\n", client_num);
}
}
// close all clients
for (int i = 0; i < client_num; i++)
{
closesocket(client_inf_vector[i].client_socket);
}
closesocket(slisten);//close the server listening service
WSACleanup();
return 0;
}