C/C++服务器基础(网络、协议、数据库)

Socket

Socket是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。它可以看成是两个网络应用程序进行通信时,各自通信连接中的端点。Socket上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口
App通过Socket发送和接收数据,主要提供了TCP Socket和UDP Socket来收发数据,基于Socket对象操作系统提供了一系列接口来收发数据。
下面提供客户端和服务器代码及其讲解:

客户端

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#ifdef WIN32 // WIN32 宏, Linux宏不存在
#include <WinSock2.h>
#include <Windows.h>
#pragma comment (lib, "WSOCK32.LIB")
#endif


int main(int argc, char** argv) {
   
	int ret;
	// 配置一下windows socket 版本
	// 一定要加上这个,否者低版本的socket会出很多莫名的问题;
#ifdef WIN32
	WORD wVersionRequested;
	WSADATA wsaData;
	wVersionRequested = MAKEWORD(2, 2);
	ret = WSAStartup(wVersionRequested, &wsaData);
	if (ret != 0) {
   
		printf("WSAStart up failed\n");
		system("pause");
		return -1;
	}
#endif

	int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (s == INVALID_SOCKET) {
   
		goto failed;
	}
	// 配置一下要连接服务器的socket
	// 127.0.0.1 本机IP地址;
	struct sockaddr_in sockaddr;
	sockaddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	sockaddr.sin_family = AF_INET;
	sockaddr.sin_port = htons(6080); // 连接信息要发送给监听socket;
	// 发送连接请求到我们服务端的监听socket;
	ret = connect(s, &sockaddr, sizeof(sockaddr));
	if (ret != 0) {
   
		goto failed;
	}

	// 连接成功, s与服务器对应的socket就会建立连接;
	// 客户端在连接的时候他也需要一个IP地址+端口;
	// 端口是服务器端口。不是,客户端一个没有使用的端口就可以了;
	// 客户端自己也会分配一个IP + 端口(只要是没有使用的就可以了);
	// 
	char buf[11];
	memset(buf, 0, 11);
	send(s, "Hello", 5, 0);
	recv(s, buf, 5, 0);
	printf("%s\n", buf);

failed:
	if (s != INVALID_SOCKET) {
   
		closesocket(s);
		s = INVALID_SOCKET;
	}
	
#ifdef WIN32
	WSACleanup();
#endif

	system("pause");
	return 0;
}


服务器

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// 配置windows socket环境
#ifdef WIN32 // WIN32 宏, Linux宏不存在
#include <WinSock2.h>
#include <Windows.h>
#pragma comment (lib, "WSOCK32.LIB")
#endif
// end 


int main(int argc, char** argv) {
   
	int ret;
	// 配置一下windows socket 版本
	// 一定要加上这个,否者低版本的socket会出很多莫名的问题;
#ifdef WIN32
	WORD wVersionRequested;
	WSADATA wsaData;
	wVersionRequested = MAKEWORD(2, 2);
	ret = WSAStartup(wVersionRequested, &wsaData);
	if (ret != 0) {
   
		printf("WSAStart up failed\n");
		system("pause");
		return -1;
	}
#endif

	// step1 创建一个监听的socket;
	int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // TCP
	if (s == INVALID_SOCKET) {
    // 创建
		goto failed;
	}
	// end 

	// ip地址 + 端口,监听到哪个IP地址和端口上;
	struct sockaddr_in sockaddr;
	sockaddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	sockaddr.sin_family = AF_INET;
	sockaddr.sin_port = htons(6080); // 127.0.0.1: 6080端口上;
	ret = bind(s, (const struct sockaddr*)&sockaddr, sizeof(sockaddr));
	if (ret != 0) {
   
		goto failed;
	}
	// 开启监听
	ret = listen(s, 1); 

	while (1) {
   
		// 等待客户介入进来;
		struct sockaddr_in c_address; // 客户端的IP地址;
		int address_len = sizeof(c_address);
		// cs 是我们服务端为客户端创建的配对的socket;
		// c_address 就是我们客户端的IP地址和端口;
		printf("waiting....!!!!\n");
		int cs = accept(s, (struct sockaddr*)&c_address, &address_len); // 在这里像卡住了一样的;OS
		printf("new client %s:%d\n", inet_ntoa(c_address.sin_addr), ntohs(c_address.sin_port));

		// 收数据;
		char buf[11];
		memset(buf, 0, 11);
		recv(cs, buf, 5, 0);
		printf("recv: %s\n", buf);
		// end 

		// 发数据给客户端
		send(cs, buf, 5, 0);
		// end 
		closesocket(cs);
	}
	
failed:
	if (s != INVALID_SOCKET) {
   
		closesocket(s);
	}
// 结束的时候也要清理
#ifdef WIN32
	WSACleanup();
#endif
// end 
	system("pause");
	return 0;
}

说明

这两个部分的代码实现了客户端和服务器的最简单通信,先运行服务器程序监听端口,再打开客户端程序,客户端程序会与服务器建立TCP连接,并发送“Hello”字符串,服务器收到后会回复一个“Hello”给客户端

Select管理模型

用于监听所有Socket,在服务器监听连接端口的同时,能够接收所有客户端传过来的数据。

步骤:

  1. 准备一个句柄集合
  2. 将Socket句柄加入到这个集合
  3. 调用Select函数等待在这个集合上
  4. 当其中一个句柄有时间发生的时候,OS唤醒任务从Select返回
  5. 处理事件,继续Select

根据上一小节服务器代码修改:

  static int client_fd[4096];
  static int socket_count = 0;
  #define MAX_BUF_LEN 4096
  static unsigned char recv_buf[MAX_BUF_LEN];

  ret = listen(s, 1); 
  fd_set set;
  while (1) {
   

    FD_ZERO(&set);
    FD_SET(s,&set);    // 监听句柄加入到等待集合
    // 客户端介入进来的socket,加入到句柄集合
    for(int j = 0; j < socket_count; j++){
   
      if(clint_fd[j] != INVALID_SOCKET){
   
        FD_SET(client_fd[j],&set);
      }
    }
    ret = select(0,&set, NULL,NULL,NULL);  // 这里监听事件的发生
    if(ret < 0){
   
      printf("select error\n");
      continue;
    }
    else if(ret == 0){
   
      printf("select timeout\n");
      continue;
    }

    if(FD_ISSET(s, &set)){
     // 发送过来连接请求
      struct sockaddr_in c_address; // 客户端的IP地址;
      int address_len = sizeof(c_address);
      printf("waiting....!!!!\n");
      int cs = accept(s, (struct sockaddr*)&c_address, &address_len);
      printf("new client %s:%d\n", inet_ntoa(c_address.sin_addr), ntohs(c_address.sin_port));

      client_fd[socket_count] = cs;
      socket_count++;
      continue;

    }

    for(int j = 0; j < socket_count; j++){
   
      if(client_fd[j] != INVALID_SOCKET && FD_ISSET(client_fd[j], &set)){
   
        int len = recv(client_fd[j], recv_buf, MAX_BUF_LEN, 0);
        if(len <= 0){
   
          closesocket(client_fd[j]);
          client_fd[j] = INVALID_SOCKET;
        }
        else{
   
          recv_buf[len] = 0;
          printf("recv: %s\n", recv_buf);
          send(client_fd[j], recv_buf, len, 0);

        }
      }
    }
  }

缺点:

  1. 每次有事件都需要遍历所有句柄,性能不好
  2. 能够管理句柄的数目有限
  3. 读写仍然是同步的

IOCP管理模型

1: IOCP: 是windows针对高性能服务器做的IO的管理模式,又叫完成端口;Linux平台有类似的机制叫epoll
2: IOCP的核心模式:
1>提交请求;
2>等待结果;
3>继续提交请求;
3: 监听:
1>提交一个监听请求,使用完成端口来等待这个请求到来;
2>请求来了后,处理,继续提交请求;
4: 读取数据:
1>提交一个读取数据的请求。
2>请求完成后,处理完后继续提交;
5: 发送数据的请求:
1>提交一个发送数据的请求;
2>请求完成后,继续处理;

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/*
IOCP 只支持windows平台 Linux epoll
*/

#include <WinSock2.h>
#include <mswsock.h>
#include <windows.h>

#pragma comment(lib, "WSOCK32.lib ")
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "odbc32.lib")
#pragma comment(lib, "odbccp32.lib")

// 非常重要的数据结构;
enum {
   
	IOCP_ACCPET = 0, // 监听socket,接入请求;
	IOCP_RECV, // 读请求;
	IOCP_WRITE, // 写请求;
};

// 接收数据的时候最大的buf大小;
#define MAX_RECV_SIZE 8192

struct io_package {
   
	// 自己定义的, 一定要在第一个就要用WSAOVERLAPPED 结构体;?
	// 所有的请求的等待,都是等在这个结构对象上的,必须是第一个;
	WSAOVERLAPPED overlapped;

	// 操作类型, 监听,读,写, IOCP_ACCPET, IOCP_RECV, IOCP_WRITE
	int opt;

	// 句柄,就是我们提交请求的句柄, accept的句柄或你读写请求的句柄
	int accpet_sock;
	// 结构体,配合读写数据的时候用的bufffer;
	WSABUF wsabuffer; // wsabuffer.buf = 内存;, wsabuffer.len = MAX_RECV_SIZE;

	// 定义了一个buf,这个buf就是整正的内存;
	char pkg[MAX_RECV_SIZE];
};

// 投递一个用户的请求;
static void
post_accept(SOCKET l_sock) {
   
	// step1: 分配一个io_package 数据结构;
	struct io_package* pkg = malloc(sizeof(struct io_package));
	memset(pkg, 0, sizeof(struct io_package));

	// 初始化好了接受数据的buf --> WSABUF
	pkg->wsabuffer.buf = pkg->pkg;
	pkg->wsabuffer.len = MAX_RECV_SIZE - 1;
	pkg->opt = IOCP_ACCPET; // 请求类型;

	DWORD dwBytes = 0;
	SOCKET client = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
	int addr_size = (sizeof(struct sockaddr_in) + 16);
	pkg->accpet_sock = client; // 创建一个socket,然后客户端连接进来后,就用这个socket和我们的客户端通讯;

	// 发送一个异步的请求,来接入客户端的连接;
	AcceptEx(l_sock, client, pkg->wsabuffer.buf, 0/*pkg->wsabuffer.len - addr_size* 2*/,
		addr_size, addr_size, &dwBytes, &pkg->overlapped);
}

static void
post_recv(SOCKET client_fd) {
   
	struct io_package* io_data = malloc(sizeof(struct io_package));
	memset(io_data, 0, sizeof(struct io_package));

	io_data->opt = IOCP_RECV;
	io_data->wsabuffer.buf = io_data->pkg;
	io_data->wsabuffer.len = MAX_RECV_SIZE - 1;
	io_data->accpet_sock = client_fd;

	DWORD dwRecv = 0;
	DWORD dwFlags = 0;
	int ret = WSARecv(client_fd, &(io_data->wsabuffer),
		1, &dwRecv, &dwFlags,
		&(io_data->overlapped), NULL);
}

int main(int argc, char** argv) {
   
	// 如果你做socket那么必须要加上;
	WSADATA data;
	WSAStartup(MAKEWORD(2, 2), &data);

	// step1:创建一个完成端口;
	HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
	if (iocp == INVALID_HANDLE_VALUE) {
   
		goto failed;
	}
	// end 
	// 创建我们的监听socket,开始监听
	SOCKET l_sock = INVALID_SOCKET;
	l_sock = socket(AF_INET, SOCK_STREAM, 0);
	if (l_sock == INVALID_SOCKET) {
   
		goto failed;
	}
	struct sockaddr_in s_address;
	memset(&s_address, 0, sizeof(s_address));
	s_address.sin_family = AF_INET;
	s_address.sin_addr.s_addr = inet_addr("127.0.0.1");
	s_address.sin_port = htons(6080);

	if (bind(l_sock, (struct sockaddr *) &s_address, sizeof(s_address)) != 0) {
   
		goto failed;
	}

	if (listen(l_sock, SOMAXCONN) != 0) {
   
		goto failed;
	}
	// end

	// 让IOCP来管理我们的l_sock
	// 第三个参数,是用户传的自定义数据(一个指针带入进去),后面讲解;
	CreateIoCompletionPort((HANDLE)l_sock, iocp, (DWORD)0, 0);
	// 发送一个监听用户进来的请求;
	post_accept(l_sock); // 投递一个accpet接入请求;

	while (1) {
   
		DWORD dwTrans;
		DWORD udata;
		struct io_package* io_data;
		// 通过完成端口,来获得这个请求的结果;
		// 调用操作系统的API函数,查看,那个请求完成了;
		// 如果没有状态完成了,那么任务会挂起,等待;
		int ret = GetQueuedCompletionStatus(iocp, &dwTrans, 
			&udata, (LPOVERLAPPED*)&io_data, WSA_INFINITE);

		if (ret == 0) {
    // 意外;
			if (io_data) {
   
				if (io_data->opt == IOCP_RECV) {
   
					closesocket(io_data->accpet_sock);
					free(io_data);
				}
				else if (io_data->opt == IOCP_ACCPET) {
   
					free(io_data);
					post_accept(l_sock);
				}
			}
			continue;
		}

		if (dwTrans == 0 && io_data->opt == IOCP_RECV) {
    // 
			// 关闭socket发生了;
			closesocket(io_data->accpet_sock);
			free(io_data);
			continue;
			// end
		}

		switch (io_data->opt) {
   
			case IOCP_ACCPET: // 接入一个socket;
			{
   
				// 接入的client_socket
				int client_fd = io_data->accpet_sock;
				int addr_size = (sizeof(struct sockaddr_in) + 16);
				struct sockaddr_in* l_addr = NULL;
				int l_len = sizeof(struct sockaddr_in);
				struct sockaddr_in* r_addr = NULL;
				int r_len = sizeof(struct sockaddr_in);

				GetAcceptExSockaddrs(io_data->wsabuffer.buf,
									  0, /*io_data->wsabuffer.len - addr_size * 2, */
									  addr_size, addr_size,
									  (struct sockaddr**)&l_addr, &l_len,
									  (struct sockaddr**)&r_addr, &r_len);

				// 将新进来的client_fd,也加入完成端口,来帮助管理完成的请求;
				// 第三个参数是用户自定义数据,你可以携带自己的数据结构,client_fd;
				CreateIoCompletionPort((HANDLE)client_fd, iocp, (DWORD)client_fd, 0);
				// 投递一个读的请求;
				post_recv(client_fd);

				// 重新投递一个接入客户端请求;
				free(io_data);
				post_accept(l_sock);
			}
			break;
			case IOCP_RECV:
			{
   
				// dwTrans 读到数据的大小;
				// socket, io_data->accpet_sock;
				// Buf io_data->wsabuffer.buf, 
				io_data->pkg[dwTrans] = 0;
				printf("IOCP recv %d %s\n", dwTrans, io_data->pkg);
				send(io_data->accpet_sock, io_data->pkg, dwTrans, 0); // test

				// 再来投递下一个请求;
				DWORD dwRecv = 0;
				DWORD dwFlags = 0;
				int ret = WSARecv(io_data->accpet_sock, &(io_data->wsabuffer),
						1, &dwRecv, &dwFlags,
						&(io_data->overlapped), NULL);
					// end 
			}
			break;
		}
	}
failed:
	if (l_sock) {
   
		closesocket(l_sock);
	}

	if (iocp != INVALID_HANDLE_VALUE) {
   
		CloseHandle(iocp); // 释放关闭完成端口;
	}
	WSACleanup();
	return 0;
}

windows多线程

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <Windows.h>

/*
进程: 任务, 代码段, 数据段, 堆, 栈;
主线程: 
线程: 创建出来的OS 可以独立调度的单元;
主线程, 线程1, 线程2, ....线程3;
1: 主线程+其他的线程都共用进程的 数据段, 堆, 代码段;
2: 每一个线程都有自己独立的栈,函数调用相互不受影响;
3: 线程是OS可以调度的独立的最小的单元,在执行的过程中,
线程随时有可能切换出去,到其他的进程或现程来运行;
*/

/* CreateThread 创建一个线程;
1>线程句柄;--> HANDLE
2>线程ID;
3>线程入口函数;
*/

/*
Sleep是windows API函数,能够让我们的线程休眠多少毫秒
*/
// 开始运行我们的线程;
// 独立运行入口
// 公用了进程的代码段,数据段, 堆;
static int g_value = 10;
char* ptr = NULL;
void test_func() {
   
}

HANDLE wait_cond = INVALID_HANDLE_VALUE;
CRITICAL_SECTION lock;
/*
事件通知/等待:
线程A,等待线程B完成达到某个条件,才能够继续;
多媒体解码线程,等待输入线程输入数据,有数据了,在通知解码线程解码;

1> 创建一个事件,要求线程都可以访问;
2> 等待的线程,调用函数来等待时间;
3> 触发的线程,当条件满足后触发;
*/

/* 线程安全机制;
数据段, 堆, 代码段是公用的,所以会有一个问题;
2个线程或多个线程,同时在访问同一个资源的时候,由于线程之间随时会切换
出去,所以就会导致他们访问同样的资源可能会有冲突;
3:如果线程和线程之间,访问资源出现了冲突,那么我们这个时候要加上一个锁的机制;

1> 需要请求一个资源的时候,请求这个锁,一旦这个锁被占用了,我们就等待;
2> 如果请求成功了以后,我们就处理,处理完了以后我们释放这个锁,
等待这个锁的线程就会被唤醒;
3> 锁就能保证我们在处理共享资源的时候,同时只有一个线程在处理,
只有等这个线程处理完了,才能够被其他的线程处理;
4> 线程同步,线程同步锁;
5> 在锁的作用下,保证了我们的公共资源的同步;
*/

/* 线程死锁: 线程之间的互相傻等 死锁;
A 锁1,锁2 .... 释放锁2,锁1;
// B 锁2,锁1 ... 释放锁2,锁1;
B 锁1,锁2 ... 释放锁2,锁1;
就是要使用同样的顺序来获得我们多个锁;
*/
DWORD WINAPI thread_entry(LPVOID lpThreadParameter) {
   

	printf("threadid %d\n", GetCurrentThreadId());
	g_value = 9; // 和进程共用数据段;
	ptr[0] = 10; // 和进程共用堆;
	test_func(); // 和进程共用代码段;
	// 线程是OS独立的调度单元;
	
	Sleep(5000);
	SetEvent(wait_cond);

	while (1) {
   
		printf("thread called\n");
		//

		EnterCriticalSection(&lock); // 没有请求到,线程挂起,指导其他的线程释放了这个锁;
		g_value = 10;
		LeaveCriticalSection(&lock);

		Sleep(3000);
	}

	return 0;
}

int main(int argc, char** argv) {
   
	ptr = malloc(100);
	// 不用手动的重置这个时间, bManualReset 是否人工重置;
	// ResetEvent(事件句柄);
	wait_cond = CreateEvent(NULL
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

微笑小星

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值