作者:刘磊 2020.4.27

   参考书目:《Windows网络编程》刘琰等著

  • 并发性

并发性是TCP/IP程序的基础,服务器软件必须在程序中有专门的支持并发的硬件或专门的机制实现并发处理。

  • 多线程编程要点

1、程序、进程和线程、

1)程序

程序是计算机指令的集合,它以文件形式存储在磁盘上。

2)进程

进程是程序在其自身的地址空间中的一次执行活动。

进程由两部分组成:

*内核对象。内核对象是系统用来存放关于进程统计信息的区域。

*地址空间。地址空间包含所有可执行模块或DLL模块的代码和数据,还包含动态内存分配的空间,如线程堆栈和堆分配空间。

进程是线程的容器,进程若要完成某项操作,必须拥有一个在它环境中运行的线程,这些线程都并发的执行进程空间中的代码。

3)线程

系统从进程的地址空间中分配内存供线程的堆栈使用。线程可以访问进程内核对象的所有句柄、进程中的所有内存和在相同进程中的其他线程的堆栈,这使得单个进程中的多个线程能够互相通信。

线程由两个部分组成:

*内核对象。存放线程统计信息的区域。

*线程堆栈。线程堆栈维护线程在执行代码时所需要的所有参数和局部变量。

线程并发并不是指同一时间内CPU同时运行多个线程。

2、多线程编程

1)创建线程函数CreateThread()

在其调用进程的进程空间里创建一个新的线程,并返回已建线程的句柄。

2)线程挂起函数SuspendThread()

挂起。

3)挂起恢复函数ResumeThread()

4)线程结束函数ExitThread()

正常结束。

5)线程终止函数TerminateThread()

强行终止某一线程的执行。

6)线程消息传递函数PostThreadMessage()

将一个消息放入到指定线程的消息队列中,并且不等到消息被该线程处理时便返回。

3、MFC对多线程编程的支持

MFC中有两类线程,工作线程和用户界面线程,主要区别是工作线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。

工作线程主要用于执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等。

用户界面线程一般用于处理独立于其他线程执行之外的用户输入。

4、线程间通信

一般来说,应用程序中的一个从线程总是为主线程执行特定的任务。

1)使用全局变量通信

定义一个全局变量,用volatile修饰符,它告诉编译器无需对该变量作任何优化,并且该值可被外部改变。

2)使用自定义消息通信

通过消息机制完成。

5、线程的同步

使隶属于同一进程的各线程协调一致工作称为线程的同步。MFC提供了多种同步对象,常用的同步对象如下:

1)临界区

当多个线程访问一个独占性共享资源时,使用临界区对象,任一时刻只有一个线程可以拥有临界区对象,其他希望进入临界区的线程将被挂起等待,直到拥有临界区的线程放弃临界区时为止,这样就保证了不会在同一时刻出现多个线程访问共享资源。

2)事件

CEvent类提供了对事件(CEvent)的支持,事件是允许一个线程在某种情况发生时候唤醒另外一个线程的同步对象。例如:A线程负责监听通信端口,B线程负责更新用户数据,通过CEvent类,A可以通知B何时更新用户数据。

每一个CEvent对象可以有两种状态:置信状态和非置信状态。

CEvent类对象有两种类型:人工事件和自动事件。

3)互斥

互斥对象和临界区对象比较类似,其差别在于,互斥对象不仅可以在同一进程内的各个线程间使用,还可以在进程间使用,而临界区对象只能在同一进程的各个线程间使用。临界区比互斥更节省系统资源。

4)信号量

信号量指被CSemaphore类对象所控制的资源可以同时接受访问的最大线程数。

  • 工作线程函数

    UINT tcp_server_fun_echo(LPVOID pParam)
    {int iResult = 0;char recvline[MAXLINE];int err;//将输入参数转换为连接套接字SOCKET s = *((SOCKET *)pParam);do {memset(recvline, 0, MAXLINE);//接收数据iResult = recv(s, recvline, MAXLINE, 0);if (iResult > 0){printf("服务器接收到数据: %s\n",recvline);//回射发送已收到的数据iResult = send(s, recvline, MAXLINE, 0);if (iResult == SOCKET_ERROR){printf("send 函数调用错误,错误号:%d\n", WSAGetLastError());err = closesocket(s);if(err == SOCKET_ERROR)printf("closesocket 函数调用错误,错误号:%d\n", WSAGetLastError());iResult = -1;}elseprintf("服务器发送数据: %s\n", recvline);}else{if (iResult == 0)printf("对方连接关闭,退出\n");else{printf("recv 函数调用错误,错误号:%d\n", WSAGetLastError());iResult = -1;}err = closesocket(s);if (err == SOCKET_ERROR)printf("closesocket 函数调用错误,错误号:%d\n", WSAGetLastError());break;}} while (iResult > 0);return iResult;
    }
  • 代码

  1. mysocket.h
#pragma once#include <time.h>
//#include <winsock2.h>
#include <stdio.h>
#include <AFXWIN.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;#define MAXLINE 4096    //接收缓冲区长度
#define LISTENQ 1024    //监听队列长度
#define SERVER_PORT 13  //时间同步服务器端口号int start_up(void);     //初始化Windows Sockets DLL;协商版本号
int clean_up(void);     //windows sockets资源释放函数
int set_address(char *hname, char * sname, struct sockaddr_in * sap, char * protocol);      //地址转换函数
int quit(SOCKET s);     //退出处理函数SOCKET tcp_server(char *hname, char *sname);        //服务器初始化函数(字符类型)
SOCKET tcp_server(ULONG uIP, USHORT uPort);         //服务器初始化函数(无符号长整型)
SOCKET tcp_client(char *hname, char *sname);        //客户端初始化函数(字符类型)
SOCKET tcp_client(ULONG uIP, USHORT uPort);         //客户端初始化函数(无符号长整型)int tcp_server_fun_echo(SOCKET s);      //回射函数
int tcp_client_fun_echo(FILE *fp,SOCKET s);     //回射函数
UINT tcp_server_fun_echo(LPVOID pParam);        //工作线程完成了在特定连接上接收客户端数据,并将数据发回的功能,直到客户端关闭连接或网络操作发生错误

2.mysocket.cpp

#include "mysocket.h"//初始化Windows Sockets DLL;协商版本号
int start_up(void)
{//初始化Windows Sockets DLL;协商版本号WORD wVersionRequested;WSADATA wsaData;int iResult;//使用MAKEWORD(lowbyte, highbyte) 宏,在windef.h中声明wVersionRequested = MAKEWORD(2, 2);iResult = WSAStartup(wVersionRequested, &wsaData);if (iResult != 0){printf("WSAStartup函数调用错误,错误号:%d\n", WSAGetLastError());return -1;}else{printf("初始化成功!\n");}return 0;
}//windows sockets资源释放函数
int clean_up(void)
{int iResult;iResult = WSACleanup();if (iResult == SOCKET_ERROR){printf(" WSACleanup函数调用错误,错误号:%d\n", WSAGetLastError());return 1;}elseprintf("WSACleanup 函数调用错误,错误号:%d\n", WSAGetLastError());return 0;
}//地址转换函数
int set_address(char *hname, char * sname, struct sockaddr_in * sap, char * protocol)
{struct servent *sp;struct hostent *hp;char *endptr;unsigned short port;unsigned long ulAddr = INADDR_NONE;//将地址结构socketaddr_in初始化为0,并设置地址为AF_INETmemset( sap, 0, sizeof( *sp));sap->sin_family = AF_INET;if (hname != NULL){//如果hname不为空,假定给出的hname为点分十进制表示的数字地址,转换地址为sockaddr_in类型ulAddr = inet_addr(hname); if (ulAddr == INADDR_NONE || ulAddr == INADDR_ANY){//调用错误,表明给出的是主机名,调用gethostbyname获得主机地址hp = gethostbyname(hname); if (hp = NULL){printf("未知的主机名,错误号:%d\n", WSAGetLastError());return -1;}sap->sin_addr = *(struct in_addr *)hp->h_addr; }elsesap->sin_addr.S_un.S_addr = ulAddr;}elsesap->sin_addr.s_addr = htonl(INADDR_ANY);port = (unsigned short)strtol(sname, &endptr, 0);if (*endptr == '\0'){sap->sin_port = htons(port);}else{sp = getservbyname(sname, protocol);if (sp == NULL){printf("未知的服务,错误号;%d\n",WSAGetLastError());return -1;}sap->sin_port = sp->s_port;}return 0;
}//退出处理函数
int quit(SOCKET s)
{int iResult = 0;iResult = closesocket(s);if (iResult == SOCKET_ERROR){printf("closesocket 函数调用错误,错误号:%d\n", WSAGetLastError());return -1;}iResult = clean_up();return iResult;
}//服务器初始化函数(字符类型)
SOCKET tcp_server(char *hname, char *sname)
{sockaddr_in local;SOCKET ListenSocket;const int on = 1;int iResult = 0;//为服务器的本地地址local设置用户输入的IP和端口号if (set_address(hname, sname, &local, "tcp") != 0)return -1;//创建套接字ListenSocket = socket(AF_INET, SOCK_STREAM, 0);if (ListenSocket == INVALID_SOCKET){printf("socket函数调用错误,错误号:%d\n",WSAGetLastError());clean_up();return -1;}//绑定服务器地址iResult = bind(ListenSocket, (struct sockaddr *) & local, sizeof(local));if (iResult == SOCKET_ERROR){printf("bind 函数调用错误,错误号:%d\n", WSAGetLastError());quit(ListenSocket);return -1;}//设置服务器位监听状态,监听队列长度为LISTENQiResult = listen(ListenSocket, SOMAXCONN);if (iResult == SOCKET_ERROR){printf("listen 函数调用错误,错误号:%d\n", WSAGetLastError());quit(ListenSocket);return -1;}return ListenSocket;
}//服务器初始化函数(无符号长整型)
SOCKET tcp_server(ULONG uIP, USHORT uPort)
{sockaddr_in local;SOCKET ListenSocket;const int on = 1;int iResult = 0;//为服务器的本地地址local设置用户输入的IP和端口号memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_addr.S_un.S_addr = htonl(uIP);local.sin_port = htons(uPort);//创建套接字ListenSocket = socket(AF_INET, SOCK_STREAM, 0);if (ListenSocket == INVALID_SOCKET){printf("socket函数调用错误,错误号:%d\n", WSAGetLastError());clean_up();return -1;}//绑定服务器地址iResult = bind(ListenSocket, (struct sockaddr *) & local, sizeof(local));if (iResult == SOCKET_ERROR){printf("bind 函数调用错误,错误号:%d\n", WSAGetLastError());quit(ListenSocket);return -1;}//设置服务器位监听状态,监听队列长度为LISTENQiResult = listen(ListenSocket, SOMAXCONN);if (iResult == SOCKET_ERROR){printf("listen 函数调用错误,错误号:%d\n", WSAGetLastError());quit(ListenSocket);return -1;}return ListenSocket;
}//客户端初始化函数(字符类型)
SOCKET tcp_client(char *hname, char *sname)
{struct sockaddr_in peer;SOCKET ClientSocket;int iResult = 0;//为服务器的本地地址local设置用户输入的IP和端口号if (set_address(hname, sname, &peer, "tcp") != 0)return -1;//创建套接字ClientSocket = socket(AF_INET, SOCK_STREAM, 0);if (ClientSocket == INVALID_SOCKET){printf("socket函数调用错误,错误号:%d\n", WSAGetLastError());clean_up();return -1;}//绑定服务器地址iResult = connect(ClientSocket, (struct sockaddr *) & peer, sizeof(peer));if (iResult == SOCKET_ERROR){printf("bind 函数调用错误,错误号:%d\n", WSAGetLastError());quit(ClientSocket);return -1;}return ClientSocket;
}//客户端初始化函数(无符号长整型)
SOCKET tcp_client(ULONG uIP, USHORT uPort)
{struct sockaddr_in peer;SOCKET ClientSocket;int iResult = 0;//为服务器的本地地址local设置用户输入的IP和端口号memset(&peer, 0, sizeof(peer));peer.sin_family = AF_INET;peer.sin_addr.S_un.S_addr = htonl(uIP);peer.sin_port = htons(uPort);//创建套接字ClientSocket = socket(AF_INET, SOCK_STREAM, 0);if (ClientSocket == INVALID_SOCKET){printf("socket函数调用错误,错误号:%d\n", WSAGetLastError());clean_up();return -1;}//绑定服务器地址iResult = connect(ClientSocket, (struct sockaddr *) & peer, sizeof(peer));if (iResult == SOCKET_ERROR){printf("bind 函数调用错误,错误号:%d\n", WSAGetLastError());quit(ClientSocket);return -1;}return ClientSocket;
}//回射函数
int tcp_server_fun_echo(SOCKET s)
{int iResult = 0;char recvline[MAXLINE];do {memset(recvline, 0, MAXLINE);//接收数据iResult = recv(s, recvline, MAXLINE, 0);if (iResult > 0){printf("服务器接收到数据%s\n", recvline);//回射发送已收到的数据iResult = send( s, recvline, iResult, 0);if (iResult == SOCKET_ERROR){printf("send 函数调用错误,错误号:%d\n", WSAGetLastError());return -1;}elseprintf("对方连接关闭,退出\n");}else{if (iResult == 0)printf("对方连接关闭,退出\n");else{printf("recv 函数调用错误,错误号:%d\n", WSAGetLastError());return -1;}break;}} while (iResult > 0);return iResult;
}//回射函数
int tcp_client_fun_echo(FILE *fp, SOCKET s)
{int iResult;char sendline[MAXLINE], recvline[MAXLINE];memset(sendline, 0, MAXLINE);memset(recvline, 0, MAXLINE);while (fgets(sendline, MAXLINE, fp) != NULL){if (*sendline == 'Q'){printf("input end!\n");iResult = shutdown(s, SD_SEND);if (iResult == SOCKET_ERROR){printf("shutdown failed with error:%d\n", WSAGetLastError());}return 0;}iResult = send(s, sendline, strlen(sendline), 0);if (iResult == SOCKET_ERROR){printf("send 函数调用错误,错误号:%d\n", WSAGetLastError());return -1;}printf("\r\n客户端发送数据:%s\r\n", sendline);memset(recvline,0,MAXLINE);iResult = recv(s,recvline,MAXLINE,0);if (iResult > 0)printf("客户端接收到数据 %s \r\n",recvline);else{if (iResult == 0)printf("服务器终止!\n");elseprintf("recv 函数调用错误,错误号:%d\n", WSAGetLastError());break;}memset(sendline, 0, MAXLINE);}return iResult;
}//工作线程完成了在特定连接上接收客户端数据,并将数据发回的功能,直到客户端关闭连接或网络操作发生错误
UINT tcp_server_fun_echo(LPVOID pParam)
{int iResult = 0;char recvline[MAXLINE];int err;//将输入参数转换为连接套接字SOCKET s = *((SOCKET *)pParam);do {memset(recvline, 0, MAXLINE);//接收数据iResult = recv(s, recvline, MAXLINE, 0);if (iResult > 0){printf("服务器接收到数据: %s\n",recvline);//回射发送已收到的数据iResult = send(s, recvline, MAXLINE, 0);if (iResult == SOCKET_ERROR){printf("send 函数调用错误,错误号:%d\n", WSAGetLastError());err = closesocket(s);if(err == SOCKET_ERROR)printf("closesocket 函数调用错误,错误号:%d\n", WSAGetLastError());iResult = -1;}elseprintf("服务器发送数据: %s\n", recvline);}else{if (iResult == 0)printf("对方连接关闭,退出\n");else{printf("recv 函数调用错误,错误号:%d\n", WSAGetLastError());iResult = -1;}err = closesocket(s);if (err == SOCKET_ERROR)printf("closesocket 函数调用错误,错误号:%d\n", WSAGetLastError());break;}} while (iResult > 0);return iResult;
}

3.server.cpp

#include "mysocket.h"
#define ECHOPORT "7210"//回射主函数
/*
int main(int argc, char *argv[])
{int iResult = 0;char ip[] = "127.0.0.1";SOCKET ListenSocket, ConnetSocket;start_up();ListenSocket = tcp_server( ip, ECHOPORT);if (ListenSocket == -1)return 1;printf("服务器准备好回射服务......\n");for (; ; ){ConnetSocket = accept(ListenSocket, NULL, NULL);if (ConnetSocket != INVALID_SOCKET){printf("\r\n建立连接成功\n\n");iResult = tcp_server_fun_echo(ConnetSocket);if (iResult == -1)printf("当前连接已关闭或出错\n");}else{printf("accept 函数调用错误,错误号:%d\n",WSAGetLastError());quit(ListenSocket);return 1;}if(closesocket(ConnetSocket) == SOCKET_ERROR)printf("closesocket 函数调用错误,错误号:%d\n", WSAGetLastError());}quit(ListenSocket);return 0;}
*///并发回射主函数
int main(int argc, TCHAR* argv[], TCHAR* envp[])
{int nRetCode = 0;//初始化MFC,并在失败时显示错误if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0)){printf(("错误:MFC初始化失败\n"));nRetCode = 1;}else{int iResult = 0;char ip[] = "127.0.0.1";SOCKET ListenSocket, ConnetSocket;CWinThread *pThread = NULL;start_up();ListenSocket = tcp_server(ip, ECHOPORT);if (ListenSocket == -1)return -1;printf("服务器准备好回射服务......\n");for (; ; ){ConnetSocket = accept(ListenSocket, NULL, NULL);if (ConnetSocket != INVALID_SOCKET){printf("\r\n建立连接成功\n\n");pThread = AfxBeginThread(tcp_server_fun_echo, &ConnetSocket);}else{printf("accept 函数调用错误,错误号:%d\n", WSAGetLastError());quit(ListenSocket);return -1;}}return nRetCode;}
}

4.client.c

#include "mysocket.h"
#define ECHOPORT "7210"int main(int argc, char *argv[])
{int iResult = 0;SOCKET ClientSocket;start_up();printf("连接建立成功,请输入回射字符串......\n");ClientSocket = tcp_client((char *)argv[1], ECHOPORT);if (ClientSocket == -1)return 1;iResult = tcp_client_fun_echo(stdin,ClientSocket);quit(ClientSocket);return iResult;}//工作线程完成了在特定连接上接收客户端数据,并将数据发回的功能,直到客户端关闭连接或网络操作发生错误
UINT tcp_server_fun_echo(LPVOID pParam)
{int iResult = 0;char recvline[MAXLINE];int err;//将输入参数转换为连接套接字SOCKET s = *((SOCKET *)pParam);do {memset(recvline, 0, MAXLINE);//接收数据iResult = recv(s, recvline, MAXLINE, 0);if (iResult > 0){printf("服务器接收到数据: %s\n", recvline);//回射发送已收到的数据iResult = send(s, recvline, MAXLINE, 0);if (iResult > 0){printf("send 函数调用错误,错误号:%d\n", WSAGetLastError());err = closesocket(s);if (err == SOCKET_ERROR)printf("closesocket 函数调用错误,错误号:%d\n", WSAGetLastError());iResult = -1;}elseprintf("服务器发送数据: %s\n", recvline);}else{if (iResult == 0)printf("对方连接关闭,退出\n");else{printf("recv 函数调用错误,错误号:%d\n", WSAGetLastError());iResult = -1;}err = closesocket(s);if (err == SOCKET_ERROR)printf("closesocket 函数调用错误,错误号:%d\n", WSAGetLastError());break;}} while (iResult > 0);return iResult;
}

Socket基础四:基于流式套接字的网络程序(并发服务器设计)相关推荐

  1. 【计算机网络】--- 流式套接字通信

    流式套接字通信 引言 TCP协议的传输特点(面试官常考点) TCP的首部 TCP首部个字段的含义如下(大致掌握) TCP连接的建立和终止(面试官必考) "三次握手".如下图所示 注 ...

  2. TCP流式套接字的异步事件WSAAsyncSelect编程

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! WSAA ...

  3. 创建一个TCP流式套接字

    #python网络套接字模块 from socket import *HOST = '172.60.50.218' PORT = 8888 ADDR = (HOST,PORT) BUFFERSIZE ...

  4. 流式套接字(SOCK_STREAM),数据报套接字 (SOCK_DGRAM) 的比较

    1.流式套接字 使用这种套接字时,数据在客户端是顺序发送的,并且到达的顺序是一致的.比如你在客户端先发送1,再发送2,那么在服务器端的接收顺序是先接收到1,再接收到2,流式套接字是可靠的,是面向连接的 ...

  5. 流式套接字:基于TCP协议的Socket网络编程(案例2)

    案例:在案例1的基础上实现一个服务器对应多个客户端(多线程),且获得每个客户端的IP. 线程代码: package com.yh.mySocket;import java.io.BufferedRea ...

  6. 2-2:套接字(Socket)编程之深入了解套接字

    文章目录 (1)Socket背景知识 (2)深入了解套接字 (3)套接字的三种类型 A:流式套接字(SOCK_STREAM) B:数据包套接字(SOCK_DGRAM) C:原始套接字 (1)Socke ...

  7. TCP套接字(网络)编程

    简单的TCP网络编程 本文中用到的内容可在另一篇博客中见到UDP套接字编程 一. 初识TCP协议 TCP叫做传输控制协议.它具有以下特点: (1)传输层的协议 (2)面向连接:基于TCP进行网络通信时 ...

  8. 网络编程(socket)套接字之基于udp的套接字

    基于udp的网络编程 特点:不需要提前建立链接,每次收发都需要获取ip和端口 又称数据报协议,一次发送对应一次接收,不会产生粘包问题    不可靠传输,发送数据不需要回复ACK确认信息    没有链接 ...

  9. 2017云栖大会·杭州峰会:《在线用户行为分析:基于流式计算的数据处理及应用》之《数据可视化:构建实时动态运营数据分析大屏》篇...

    实验背景介绍 了解更多2017云栖大会·杭州峰会 TechInsight & Workshop. 本手册为云栖大会Workshop之<在线用户行为分析:基于流式计算的数据处理及应用> ...

最新文章

  1. 程序员颈椎病康复秘籍
  2. Kubernetes——自动扩展容器!假设你突然需要增加你的应用;你只需要告诉deployment一个新的 pod 副本总数即可...
  3. 问题-[Delphi]MainFrame.pas(4340): E2036 Variable required
  4. 新一代神器STM32CubeMonitor介绍、下载、安装和使用教程
  5. scss2css vscode设置_VSCode下让CSS文件完美支持SCSS或SASS语法方法
  6. linux下强行umount卸载设备
  7. java tree 表格_00030-layui+java 树形表格treeTable
  8. php 小说采集系统,YGBOOK小说采集系统 php版 v1.4
  9. UVC协议学习2--UVC请求格式分析
  10. 吝啬SAT问题是NP完全问题的证明
  11. 使用视频追踪算法研究物体运动轨迹
  12. 牛刀小试imageROI
  13. hbuilder TODO插件
  14. 阿里钉钉、蚂蚁、饿了么,淘宝真实面试分享
  15. java中线程池的实现原理:七参、四策
  16. android profile 打包_Android 利用 Managed Profile 确保兼容性
  17. Consul + fabio 实现自动服务发现、负载均衡 - DockOne.io
  18. 最全java面试题汇总(带答案)
  19. 0元永久授权,etl作业批量调度必备软件 Taskctl Free应用版
  20. MySQL运维篇之Mycat分片规则

热门文章

  1. 用python做名片_Linux下python制作名片示例
  2. Linux系统简介分区基础命令(ADMIN01-2)
  3. 问卷调查小程序功能清单
  4. 虚拟的超级计算机和云计算,概念PK:云计算与高性能计算(HPC)
  5. 【Laravel笔记】10. 模型的关联查询
  6. CT是新冠肺炎有效诊断工具
  7. java 基本类型 不赋值_探究Java中基本类型和部分包装类在声明变量时不赋值的情况下java给他们的默认赋值...
  8. java课程 数独 文库_JAVA课程设计九宫格数独.pdf
  9. 微信小程序影视评论交流平台系统毕业设计毕设(2)小程序功能
  10. Scratch3.0中保存项目时,建议使用的扩展名是sb3