文档库 最新最全的文档下载
当前位置:文档库 › Windows下的TCP回显多线程服务器

Windows下的TCP回显多线程服务器

Windows下的TCP回显多线程服务器
Windows下的TCP回显多线程服务器

一个基于TCP的阻塞模型的并发回显服务器-客户端程序

by windhawk

一、概述

网络编程普遍使用socket接口来实现网络间的进程通信,具体的TCP/IP细节被封装在内核之中,由内核完成;用户只需要使用内核提供的socket API来实现通信即可。

Windows网络编程继承了Unix的socket接口,基本模式同Unix大同小异。但是由于两个系统的实现不同,在具体实现网络程序时仍存在一些差异。包括一些socket API的原型,以及并发服务器的编写方法等。

由上表可见,winsock与Unix的socket API基本上完全一致,除了关闭套接字和I/O函数略有不同以外,实现了全部继承。

除了函数的不同,在一些细节上Windows与Unix也略有不同,比如:

1.Unix下套接字地址结构为:

struct sockaddr_in {

uint8_t sin_len; //套接字地址结构的长度,IPv4固定16个字节,IPv6固定28个字节

sa_family_t sin_family //AF_INET

in_port_t sin_port //协议端口地址,数据类型为uint6,网络字节顺序

struct in_addr sin_addr //协议IP地址,数据类型为uint32,网络字节顺序

char sin_zero[8] //unused

};

其中,

sturct in_addr {

in_addr_t s_addr;

}

Windows下的不同在于struct in_addr,

struct in_addr {

union {

struct { u_char s_b1, s_b2, s_b3, s_b4; } S_un_b;

struct { u_short s_w1, s_w2; } S_un_w;

u_long S_addr;

} S_un;

这里联合体中的S_un包含了32位IP的三种表示形式:按字符,按字,按无符号长整型,我们一般区S_addr,所以正式设置套接字地址结构IP时我们在Windows下使用的是:servaddr.sin_addr.S_un.S_addr = inet_addr(“192.168.100.254”);

2.包含的头文件

Unix下使用socket接口编写网路程序需要包含许多头文件,而Windows下基本就需要添加#include

#pragma comment(lib, “ws2_32.lib);

三、几点需要注意的问题

1.socket API函数多数是由进程向内核传递套接字结构,此时需要指明传递的结构长度,

使得内核读入指定长度的字节,如bind(), connect()。而对于accept()函数则可以由内核向进程双向返回值,所以其中的套接字地址结构和长度均需要使用指针。

2.write()/read()和send()/recv()函数都需要事先指定存储发送/读取数据的缓冲区及大小,

为了避免之后输出时的错误,我们可以用memset(buff, 0, sizeof(buff));将缓冲区初始化为空字符,接受数据时就不会因为结束字符问题导致输出错误

四、程序源码

Server程序

/*一个简单的TCP服务器程序:

1.接受客户端连接后显示客户端的输入;

2.向客户端回显该输入;

3.若收到"quit"则断开连接,退出程序;

4.创建新线程实现并发连接,客户端输入"shutdown"时关闭服务器

*/

#include

#include

#include

#include

//添加默认链接库

#pragma comment(lib, "ws2_32.lib")

#define bzero(a, b) memset(a, 0, b) //vc下没有bzero的定义,用memset代替

// void *memset(void *dst, int c, size_t len) DWORD WINAPI AnswerThread(LPVOID lparam); //线程函数声明

struct sockaddr_in cliaddr; //定义外部变量供线程函数调用

int main( int argc, char *argv[])

{

//初始化winsock版本信息,加载动态链接库(dll)

WSADATA wsaData;

if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {

printf("WSAStartup failed !\n");

return -1;

}

//创建监听套接字

SOCKET sockfd;

if ( (sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) ) == INVALID_SOCKET) { printf("socket failed !\n");

WSACleanup();

return -1;

}

//设置服务器地址

struct sockaddr_in servaddr;

bzero(&servaddr, sizeof(servaddr));

servaddr.sin_family = AF_INET;

servaddr.sin_port = htons(9999);

servaddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

//绑定socket地址结构到监听套接字

if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0) {

printf("bind failed !\n");

closesocket(sockfd);

WSACleanup();

}

//在Server上进行监听

if (listen(sockfd, 4) != 0) {

printf("listen failed !\n");

closesocket(sockfd);

WSACleanup();

return -1;

}

//接受客户端的连接请求

printf("TCP Server Start...\n");

int cliaddrlen = sizeof(cliaddr); //cliaddrlen需要有初值,作为一个值-结果参数参与函数bzero(&cliaddr, sizeof(cliaddr)); //memset(&cliaddr, 0, sizeof(cliaddr));

SOCKET connfd;

//循环等待

while (true)

{

if ((connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &cliaddrlen)) == INVALID_SOCKET) { printf("accept failed !\n");

closesocket(sockfd);

WSACleanup();

return -1;

}

//创建新线程

DWORD ThreadID; //double word

CreateThread(NULL, 0, AnswerThread, (LPVOID)connfd, 0, &ThreadID);

}

}

//线程函数AnswerThread

DWORD WINAPI AnswerThread(LPVOID lparam)

{

//在Server和Client之间接收和发送数据

char buff[100];

extern sockaddr_in cliaddr;

SOCKET connfd = (SOCKET)(LPVOID)lparam;

while (true)

{

int ret = recv(connfd, buff, sizeof(buff), 0);

if (ret == SOCKET_ERROR) {

printf("recv failed !\n");

closesocket(connfd);

WSACleanup();

return -1;

}

//为了避免打印错误,将字符串buff结尾设成0x00

buff[ret] = 0x00;

//获取当前系统时间

SYSTEMTIME st;

GetLocalTime(&st);

char SysDate[30];

//将systime中的时间转变为字符串存入SysDate[30];

sprintf(SysDate, "%4d-%2d-%2d %2d:%2d:%2d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);

//Server显示客户端信息

printf("%s Recv from Client [%s:%d] : %s\n", SysDate, inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port), buff);

//服务器向客户端回显信息

//如果客户端发送"quit"字符串,则服务器退出

if (strcmp(buff, "quit") == 0) {

send(connfd, "quit", strlen("quit"), 0);

break;

}

//否则向客户端回显字符串

else if (strcmp(buff, "shutdown") == 0) {

send(connfd, "shutdown server", strlen("shutdown server"), 0);

printf("Server is shutdowning...\n");

WSACleanup();

system("pause");

break;

}

else

{

char msg[100];

sprintf(msg, "Message Received : %s", buff);

if (send(connfd, msg, strlen(msg), 0) == SOCKET_ERROR) {

printf("send failed !\n");

closesocket(connfd);

return -1;

}

}

}

//释放资源

closesocket(connfd);

}

Client程序:

/*一个简单的TCP客户端连接程序:

1.客户端向服务器端发送输入的字符串;

2.接受服务器端的回显

*/

#include

#include

#include

#include

#define MAXSIZE 100

//引用外部函数时需要加上extern关键字,定义函数时默认均是extern extern int getline(char *s, int max);

int main(int argc, char *argv[])

{

//初始化winsock结构,加载winsock动态库

WSAData wsaData;

if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {

printf("WSAStartup failed !\n");

return -1;

}

//创建套接字

SOCKET sockfd;

if ((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) { printf("socket failed !\n");

return -1;

}

//初始化服务器地址结构

struct sockaddr_in servaddr;

memset(&servaddr, 0, sizeof(servaddr));

servaddr.sin_family = AF_INET;

servaddr.sin_port = htons(9999);

servaddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

//发起连接请求

if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == SOCKET_ERROR) { printf("connect failed !\n");

closesocket(sockfd);

WSACleanup();

return -1;

}

else

printf("Connection build...\n");

//向服务器发送数据

char buff[MAXSIZE];

memset(buff, 0 , MAXSIZE);

while (strcmp(buff, "quit") != 0 && strcmp(buff, "shutdown") != 0) {

printf("Please input a string to send: ");

getline(buff, MAXSIZE);

if(send(sockfd, buff, strlen(buff), 0) <= 0) {

printf("send failed !\n");

closesocket(sockfd);

WSACleanup();

return -1;

}

//初始化缓冲区为0

memset(buff, 0 , MAXSIZE);

if(recv(sockfd, buff, MAXSIZE, 0) <= 0) {

printf("recv failed !\n");

closesocket(sockfd);

WSACleanup();

return -1;

}

//打印收到的回显字符串

char msg[MAXSIZE];

memset(msg, 0 , MAXSIZE);

sprintf(msg, "Received from Server [%s:%d]: %s\n", inet_ntoa(servaddr.sin_addr), ntohs(servaddr.sin_port), buff);

printf("%s\n",msg);

}

//释放资源

closesocket(sockfd);

WSACleanup();

system("pause");

return 0;

}

getline函数定义:

int getline(char *s, int max)

{

int i = 0;

char c;

while (--max >0 && (c = getchar()) != EOF && c != '\n')

s[i++] = c;

if (c = '\n')

s[i] = '\0';

return i;

}

五、程序实验

同时运行两个客户端,在服务器上看到来自两个客户端的连接

客户端退出:

服务器端继续服务

六、不足

程序虽然实现了基本的并发服务器-客户端应用,但是在事先并发机制时使用的是创建新线程的方式。实际应用中服务器不可能无限制地接受来自客户端的连接请求,也不可能无限制地创建专用通信线程。通常可以考虑引入线程池机制对专用通信线程进行管理。

实验2-2windows2000 线程同步

实验2 并发与调度 2.2 Windows 2000线程同步 (实验估计时间:120分钟) 背景知识 实验目的 工具/准备工作 实验内容与步骤 背景知识 Windows 2000提供的常用对象可分成三类:核心应用服务、线程同步和线程间通讯。其中,开发人员可以使用线程同步对象来协调线程和进程的工作,以使其共享信息并执行任务。此类对象包括互锁数据、临界段、事件、互斥体和信号等。 多线程编程中关键的一步是保护所有的共享资源,工具主要有互锁函数、临界段和互斥体等;另一个实质性部分是协调线程使其完成应用程序的任务,为此,可利用内核中的事件对象和信号。 在进程内或进程间实现线程同步的最方便的方法是使用事件对象,这一组内核对象允许一个线程对其受信状态进行直接控制 (见表4-1) 。 而互斥体则是另一个可命名且安全的内核对象,其主要目的是引导对共享资源的访问。拥有单一访问资源的线程创建互斥体,所有想要访问该资源的线程应该在实际执行操作之前获得互斥体,而在访问结束时立即释放互斥体,以允许下一个等待线程获得互斥体,然后接着进行下去。 与事件对象类似,互斥体容易创建、打开、使用并清除。利用CreateMutex() API 可创建互斥体,创建时还可以指定一个初始的拥有权标志,通过使用这个标志,只有当线程完成了资源的所有的初始化工作时,才允许创建线程释放互斥体。

为了获得互斥体,首先,想要访问调用的线程可使用OpenMutex() API来获得指向对象的句柄;然后,线程将这个句柄提供给一个等待函数。当内核将互斥体对象发送给等待线程时,就表明该线程获得了互斥体的拥有权。当线程获得拥有权时,线程控制了对共享资源的访问——必须设法尽快地放弃互斥体。放弃共享资源时需要在该对象上调用ReleaseMute() API。然后系统负责将互斥体拥有权传递给下一个等待着的线程(由到达时间决定顺序) 。 实验目的 在本实验中,通过对事件和互斥体对象的了解,来加深对Windows 2000线程同步的理解。 1) 回顾系统进程、线程的有关概念,加深对Windows 2000线程的理解。 2) 了解事件和互斥体对象。 3) 通过分析实验程序,了解管理事件对象的API。 4) 了解在进程中如何使用事件对象。 5) 了解在进程中如何使用互斥体对象。 6) 了解父进程创建子进程的程序设计方法。 工具/准备工作 在开始本实验之前,请回顾教科书的相关内容。 您需要做以下准备: 1) 一台运行Windows 2000 Professional操作系统的计算机。 2) 计算机中需安装Visual C++ 6.0专业版或企业版。 实验内容与步骤 1. 事件对象 2. 互斥体对象 1. 事件对象 清单2-1程序展示了如何在进程间使用事件。父进程启动时,利用CreateEvent() API创建一个命名的、可共享的事件和子进程,然后等待子进程向事件发出信号并终止父进程。在创建时,子进程通过OpenEvent() API打开事件对象,调用SetEvent() API使其转化为已接受信号状态。两个进程在发出信号之后几乎立即终止。 步骤1:登录进入Windows 2000 Professional。 步骤2:在“开始”菜单中单击“程序”-“Microsoft Visual Studio 6.0”–“Microsoft Visual C++ 6.0”命令,进入Visual C++窗口。

Windows多线程程序设计

Windows多线程程序设计- - 1、产生一个线程,只是个框架,没有具体实现。理解::CreateThread函数用法。 #include DWORD WINAPI ThreadFunc(LPVOID); int main() { HANDLE hThread; DWORD dwThreadID; hThread = ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)(ThreadFunc), NULL, 0, &dwThreadID); ...; return 0; } DWORD WINAPI ThreadFunc(LPVOID lParam) { ...; return 0; } 2、一个真正运转的多线程程序,当你运行它的时候,你会发现(也可能会害怕),自己试试吧。说明了多线程程序是无法预测其行为的,每次运行都会有不同的结果。 #include #include using namespace std; DWORD WINAPI ThreadFunc(LPVOID); int main() { HANDLE hThread; DWORD dwThreadID; // 产生5个线程 for(int i=0; i<5; i++)

{ hThread = ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)(ThreadFunc), (LPVOID)&i, 0, &dwThreadID); if(dwThreadID) cout << "Thread launched: " << i << endl; } // 必须等待线程结束,以后我们用更好的处理方法 Sleep(5000); return 0; } DWORD WINAPI ThreadFunc(LPVOID lParam) { int n = (int)lParam; for(int i=0; i<3; i++) { cout << n <<","<< n <<","<< n << ","< } return 0; } 3、使用CloseHandle函数来结束线程,应该是“来结束核心对象的”,详细要参见windows 多线程程序设计一书。 修改上面的程序,我们只简单的修改if语句。 if(dwThreadID) { cout << "Thread launched: " << i << endl; CloseHandle(dwThreadID); } 4、GetExitCodeThread函数的用法和用途,它传回的是线程函数的返回值,所以不能用GetExitCodeThread的返回值来判断线程是否结束。 #include #include using namespace std;

一个多线程的windows控制台应用程序

一个多线程的windows控制台应用程序 一、要求: 编写一个单进程、多线程的windows控制台应用程序。 二、平台: Window XP C# 三、内容: 每个进程都有分配给它的一个或多个线程。线程是一个程序的执行部分。 操作系统把极短的一段时间轮流分配给多个线程。时间段的长度依赖于操作系统和处理器。 每个进程都开始一个默认的线程,但是能从它的线程池中创建一个新的线程。 线程是允许进行并行计算的一个抽象概念:在一个线程完成计算任务的同时,另一个线程可以对图像进行更新,两个线程可同时处理同一个进程发出的两个网络请求。 如图所示,选择操作: 1、创建和启动一个线程。在一个进程中同时教和运行两个线程,并且可以不需要停止或者释放一个线程。 相关代码及其解释: public class Threading1:Object { public static void startup() { //创建一个线程数组 Thread[] threads=new Thread[2]; for(int count=0;count

public static void Count() { for(int count=1;count<=9;count++) Console.Write(count+" "); } } 输出结果: 这里通过new方法创建了两个线程,然后使用start()方法来启动线程,两个线程的作用是:两个线程同时从1数到9,并将结果打印出来。 运行上面的程序代码时,可能会在控制台上输出多种不同的结果。从123456789123456789到112233445566778899或121233445566778989在内的各种情况都是可能出现的,输出结果可能与操作系统的调度方式有关。 2、停止线程。当创建一个线程后,可以通过多种属性方法判断该线程是否处于活动状态,启动和停止一个线程等。相关代码及其解释: public class MyAlpha { //下面创建的方法是在线程启动的时候的时候调用 public void Beta() { while(true) { Console.WriteLine("MyAlpha.Beta is running in its own thread."); } } } public class Simple { public static int Stop() { Console.WriteLine("Thread Start/Stop/Join"); MyAlpha TestAlpha=new MyAlpha(); //创建一个线程对象 Thread MyThread=new Thread(new ThreadStart(TestAlpha.Beta)); //开起一个线程 MyThread.Start(); while(!MyThread.IsAlive);

windows 并发的多线程的应用

(1)苹果香蕉问题 #include using namespace std; #include #include int k; HANDLE Apple_;HANDLE Banana_; CRITICAL_SECTION mmutex; DWORD WINAPI Son(LPVOID n) {//HANDLE Apple_; CRITICAL_SECTION mmutex; int i=1; OpenSemaphore(MUTEX_ALL_ACCESS,false,"Apple_"); while(1) { ::WaitForSingleObject(Apple_,INFINITE);//等苹果 cout<<"Son eats"<

Windows下多线程同步机制

多线程同步机制 Critical section(临界区)用来实现“排他性占有”。适用范围是单一进程的各线程之间。它是: ·一个局部性对象,不是一个核心对象。 ·快速而有效率。 ·不能够同时有一个以上的critical section被等待。 ·无法侦测是否已被某个线程放弃。 Mutex Mutex是一个核心对象,可以在不同的线程之间实现“排他性占有”,甚至几十那些现成分属不同进程。它是: ·一个核心对象。 ·如果拥有mutex的那个线程结束,则会产生一个“abandoned”错误信息。 ·可以使用Wait…()等待一个mutex。 ·可以具名,因此可以被其他进程开启。 ·只能被拥有它的那个线程释放(released)。 Semaphore Semaphore被用来追踪有限的资源。它是: ·一个核心对象。 ·没有拥有者。 ·可以具名,因此可以被其他进程开启。 ·可以被任何一个线程释放(released)。 Ev ent Object Ev ent object通常使用于overlapped I/O,或用来设计某些自定义的同步对象。它是: ·一个核心对象。 ·完全在程序掌控之下。 ·适用于设计新的同步对象。 · “要求苏醒”的请求并不会被储存起来,可能会遗失掉。 ·可以具名,因此可以被其他进程开启。 Interlocked Variable 如果Interlocked…()函数被使用于所谓的spin-lock,那么他们只是一种同步机制。所谓spin-lock是一种busy loop,被预期在极短时间内执行,所以有最小的额外负担(overhead)。系统核心偶尔会使用他们。除此之外,interlocked variables主要用于引用技术。他们:·允许对4字节的数值有些基本的同步操作,不需动用到critical section或mutex之类。 ·在SMP(Symmetric Multi-Processors)操作系统中亦可有效运作。 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

在Windows下创建进程和线程的API

利用API在Windows下创建进程和线程 前言: 谈到在Windows创建线程的例子,在网上的很多的参考都是基于MFC的。其实,就操作系统实验这个前提而言,大可不必去碰那个大型的MFC的框架。在Windows命令控制台下可创建进程及线程,做些简单的进程及线程的测试程序。 1、实验准备: 要实验的Windows下的多线程实验,应做如下准备: a) 在新建中选”Win32 Console Application”的An empty project b) 选”工程”的”设置”选项,在”设置”中选择“C/C++”标签,在”Project Option”中,将”MLd”参数改成“MTd”(如图1)。 图1 选项 以上两步对实验成功至关重要,否则,即是代码无误,在连接时同样会出现问题。 2、Windows下进程的创建: Windows的进程和线程模型被描述成”多进程,基于单进程的多线程”。 在创建一个线程时,Windows会做大量的工作---创建一个新的地址空间,为进程分配资源以及创建一个基线程。

CreateProcess函数的原型如下: 虽然有很多参数,不过在现阶段的实验级别,大多数参数只要用默认值即可。 下面要做的关于Windows使用进程的实验,在Linux系统下,可以使用类似: execve(char* cmdName ,char* cmdArgu)的语句从一个程序中去执行其它的程序。 而如果在Windows下,当使用CreateProcess去执行相应的功能时,只要去改变cmdLine中的容即可,其它的参数使用默认值,具体见代码1: 代码1执行的功能是从命令行中启动这个名叫的launch的测试程序,在launch后面应加上保存有需要打开程序路径的文件名: 如在命令行中键入: >launch set.txt 而set.txt中的容为: C:\\WINDOWS\\SYSTEM32\\CALC.EXE C:\\WINDOWS\\SYSTEM32\\NOTEPAD.EXE NEW.TXT C:\\WINDOWS\\SYSTEM32\\CHARMAP.EXE 路径的前半部分为”C:\\WINDOWS\\”,这当然要视你的Windows系统的类型以及系统盘的存放位置而定。如果是NT或2000的机器,则应使用WINNT. /*测试程序1: 示例如使用进程的launch程序(启动程序),通过在命令行中加载相应的命令文件,去按照命令文件中指定的程序路径打开相应的程序去执行*/

Windows多线程及消息队列

1.所谓的worker线程,是指完全不牵扯到图形用户界面(GUI),纯粹做运算的线程。 2.微软的多线程模型: Win32说明文件一再强调线程分为GUI线程和worker线程两种。GUI线程负责建造窗口以及处理主消息循环。Worker负责执行纯粹的运算工作,如重新计算或重新编页等,这些运算工作会导致主线程的消息队列失去反应。一般而言,GUI线程绝不会去做那些不能够马上完成的工作。 GUI线程的定义是:拥有消息队列的线程。任何一个特定窗口的消息总是被产生这一窗口的线程抓到并处理。所有对此窗口的改变也都应该由该线程完成。 如果worker线程也产生了一个窗口,那么就会有一个消息队列随之被产生出来并且附着到此线程身上,于是worker线程摇身一变成了GUI线程。这里的意思是:worker线程不能够产生窗口、对话框、消息框,或任何其他与UI有关的东西。 如果一个worker线程需要输入或输出错误信息,它应该授权给UI线程来做,并且将结果通知给worker线程。 消息队列是一个链表,只有在必要的时候,才有元素产生出来。具体的关于消息队列的数据结构,可以参考相关的windows文档。 3.在Win32中,每一个线程有它自己专属的消息队列。这并不意味着每一个窗口有它自己的消息队列,因为一个线程可以产生许多窗口。如果一个线程停止回应,或是它忙于一段耗时的计算工作,那么由它产生的窗口统统都会停止回应,但系统中的其他窗口还会继续正常工作。 以下是一个非常基本的规则,用来管理Win32中的线程、消息、窗口的互动: 所有传送给某一窗口之消息,将由产生该窗口之线程负责处理。 比方说,使用SetWindowText来更新一个Edit框的内容,其实就是发出了一个WM_SETTEXT 消息给edit窗口函数。推而广之,每一个控件都是一个窗口,都拥有自己的窗口函数。 对窗口所作的一切事情基本上都会被该窗口的窗口函数处理,并因此被产生该窗口的线程处理。当需要发送一个消息时,Windows会自动计算出哪一个线程应该接收到消息(以便确定该消息实体应该挂在在哪一个线程的消息队列中)。同时,windows还会确定线程应该如何被告知有这么一个消息进来。一共有四种可能: (1)如果属于同一线程,使用SendMessage传递消息,则直接调用窗口函数。 (2)如果属于同一线程,使用PostMessage传递消息,则把消息放在消息队列中然后立即返回。(3)如果不属于同一线程,使用SendMessage传递消息,则切换到新线程中并调用窗口函数。在该窗口函数结束之前,SendMessage不会返回。 (4)PostMessage立刻返回,消息则被放到另一线程的消息队列中。 当我send一个消息给另一线程掌握的窗口时,系统必须做一次context switch,切换到另一线程去,调用该窗口函数,然后再做一次contex t switch切换回来,相对一般的函数调用而言,期间的额外负担较大。如果在MDI中,为每个子窗口分配一个线程,那么该子窗口的所有资源——包括画刷,DC,调色板等等都属于线程的资源。此时为线程做context switch时会代价很大。

当前流行的Windows操作系统能同时运行几个程序独立运行

当前流行的Windows操作系统能同时运行几个程序(独立运行的程序又称之为进程),对于同一个程序,它又可以分成若干个独立的执行流,我们称之为线程,线程提供了多任务处理的能力。用进程和线程的观点来研究软件是当今普遍采用的方法,进程和线程的概念的出现,对提高软件的并行性有着重要的意义。现在的大型应用软件无一不是多线程多任务处理,单线程的软件是不可想象的。因此掌握多线程多任务设计方法对每个程序员都是必需要掌握的。本实例针对多线程技术在应用中经常遇到的问题,如线程间的通信、同步等,分别进行探讨,并利用多线程技术进行线程之间的通信,实现了数字的简单排序。 一、实现方法 1、理解线程 要讲解线程,不得不说一下进程,进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它系统资源组成。进程在运行时创建的资源随着进程的终止而死亡。线程的基本思想很简单,它是一个独立的执行流,是进程内部的一个独立的执行单元,相当于一个子程序,它对应于Visual C++中的CwinThread类对象。单独一个执行程序运行时,缺省地包含的一个主线程,主线程以函数地址的形式出现,提供程序的启动点,如main ()或WinMain()函数等。当主线程终止时,进程也随之终止。根据实际需要,应用程序可以分解成许多独立执行的线程,每个线程并行的运行在同一进程中。 一个进程中的所有线程都在该进程的虚拟地址空间中,使用该进程的全局变量和系统资源。操作系统给每个线程分配不同的CPU时间片,在某一个时刻,CPU只执行一个时间片内的线程,多个时间片中的相应线程在CPU内轮流执行,由于每个时间片时间很短,所以对用户来说,仿佛各个线程在计算机中是并行处理的。操作系统是根据线程的优先级来安排CPU 的时间,优先级高的线程优先运行,优先级低的线程则继续等待。 线程被分为两种:用户界面线程和工作线程(又称为后台线程)。用户界面线程通常用来处理用户的输入并响应各种事件和消息,其实,应用程序的主执行线程CWinAPP对象就是一个用户界面线程,当应用程序启动时自动创建和启动,同样它的终止也意味着该程序的结束,进程终止。工作线程用来执行程序的后台处理任务,比如计算、调度、对串口的读写操作等,它和用户界面线程的区别是它不用从CWinThread类派生来创建,对它来说最重要的是如何实现工作线程任务的运行控制函数。工作线程和用户界面线程启动时要调用同一个函数的不同版本;最后需要读者明白的是,一个进程中的所有线程共享它们父进程的变量,但同时每个线程可以拥有自己的变量。

基于Windows多线程环境下的串口通信

第46卷 第3期 武汉大学学报(自然科学版) V ol.46 N o.3 2000年6月 J.W uhan U niv.(N at.Sci.Ed.) June,2000,373~375 文章编号:0253-9888(2000)03-0373-03 基于Windows多线程环境下的串口通信 陈淑珍,石 波 (武汉大学电子信息学学院,武汉430072) 摘 要:根据串口通信的基本原理,结合W indow s环境下的多任务并发机制,采用Window s的多线程技术来实现串口动态实时通信.有效地解决了在串口通信中的实时响应问题,降低了数据的丢失率,提高了系统的可靠性.同时提出了在Window s环境下实现串口通信的一般方法和步骤.实践证明,这种结合多线程技术的串口通信方法具有很强的实用性. 关 键 词:多线程;串行通信;实时查询 中图分类号:T P311.11 文献标识码:A 在实际的工程应用中,应用程序经常需要具备与外围设备进行通信的能力.在与串口,调制解调器,或是通过电话线进行通信的应用程序中,异步串行通信是一种重要的通信手段.在单任务的操作系统中,应用程序不能处理通信过程中的突发和并发事件,这种缺陷会引起数据丢失和不可靠性.而Window s基于线程的多任务并发机制使得应用程序能同时执行不同的任务,达到了降低数据丢失率,提高系统可靠性的目的. 1 串口通信的原理和机制 不论何种通信,背后都需要一个通信协议的支持.串口通信大多采用了美国电子工业协会(EIA)于1969年制定的RS-232标准[1].RS-232标准规定了数据终端设备和数据通信设备之间的连接和通信规则.该协议运用RTS(Request to Send)和(Clear to Send)信号来实现串口和外围设备的硬件“握手”,从而建立通信双方的连接和应答.在通信的连接和应答完成以后,双方就可以在误差允许范围内进行串行通信. 由于Window s是一个基于消息驱动的操作系统,它的很多消息是从硬件反馈过来的.Window s 不允许程序开发人员直接和硬件打交道,在串口通信方面提供了一组API系统函数来管理串口,这对减低编程工作量,提高系统的稳定性和安全性都是很有好处的. 在Win9x操作系统中,对串行通讯设备的操作如同文件的操作一样:串行通讯设备的打开、读写和关闭等操作均与文件操作相同,这和以前Win3x中的通信方式不同.由于w in9x系统中取消了串行通讯中的特定消息WM_COMM NOTIFY(外围通讯设备一有相应事件发生,该消息就会被传送),使得应用程序工作于“事件驱动”方式时,应创建专用的线程来监视有关的串行通讯设备. ●打开和关闭串口.通信会话以调用CreateFile函数开始,为读、写操作打开串口.为实现串口的排他性访问,共享标志应设置成false,创建标志应为o pen_exiting,模板句柄应为null,同时返回串口句柄. 通信会话通过调用CloseHandle函数来关闭串口占用的内存句柄,释放相应的串口资源. ●初始化和配置串口.一旦串口处于打开状态,Window s就可以给串口分配接受和发送缓冲区.缓冲区的大小既可以缺省,也可以指定(调用SetupComm函数). 配置串口需要设置串口通讯中特定事件的掩码(调用SetCom mM ask),只要串口中出现特定的消 收稿日期:2000-02-22  基金项目:九五国家重点科技攻关项目(204980340) 作者简介:陈淑珍(1946-),女,教授,现从事计算机网络与多媒体研究.

windows环境下C语言多线程实现网络编程,多人聊天室,

/*********************** 服务器 ************************/ #include "stdafx.h" #include #include #include #include "my_typedef.h" #pragma comment(lib,"ws2_32.lib") #define L_MAX (255) #define C_MAX (100) DWORD WINAPI ThreadProc( /* 线程函数*/ LPVOID lpParam ); HANDLE tThread_Client[C_MAX] = {NULL}; LNode *pHead; int main(int argc, char* argv[]) { //初始化WSA WORD sockVersion = MAKEWORD(2,2); WSADATA wsaData; if(WSAStartup(sockVersion, &wsaData)!=0) { return 0; } //创建套接字 SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(slisten == INV ALID_SOCKET) { printf("socket error !"); return 0; } //绑定IP和端口 sockaddr_in sin;

sin.sin_family = AF_INET; sin.sin_port = htons(8888); sin.sin_addr.S_un.S_addr = INADDR_ANY; if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR) { printf("bind error !"); } //开始监听 if(listen(slisten, C_MAX) == SOCKET_ERROR) { printf("listen error !"); return 0; } sockaddr_in remoteAddr; int nAddrlen = sizeof(remoteAddr); SOCKET sClient = NULL; SOCKET Socket_Sclient = NULL; /* 创建一个链表存放已经连接的客户端*/ pHead = (LNode*)malloc(sizeof(LNode)); pHead->pNext = NULL; pHead->sClient = NULL; /*循环等待客户端连接,并在连接后创建一个SOCKET和线程*/ while (true) { int i = 0; printf("等待连接...\n"); sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen); if(sClient == INV ALID_SOCKET) { printf("accept error !"); continue; } else { Socket_Sclient = sClient; add_Element(pHead,sClient); tThread_Client[i] = CreateThread(NULL, 0, ThreadProc,(LPVOID)Socket_Sclient,

Windows多线程编程_C语言

Windows多线程编程-C语言 先上代码: #include #include// for HANDLE #include// for _beginthread() #include unsigned__stdcall thread(void * i) //子线程入口函数 { int * k = (int *)i; printf("这是子线程%d\n", *k); return 1; // the thread exit code } int main() { HANDLE hth1;//子线程句柄 unsigned Thread1ID;//子线程ID int i = 1;//子线程入口函数参数 //创建子线程 hth1 = (HANDLE)_beginthreadex(NULL, // security,安全属性 0, // stack size thread, //子线程入口函数 &i, // arg list 入口函数参数地址 CREATE_SUSPENDED, //先挂起该线程 &Thread1ID);//线程标识符 if (hth1 == 0)//如果返回的hth1的值为0 则表示创建子线程失败 printf("Failed to create thread 1\n"); DWORD ExitCode; //线程退出码 GetExitCodeThread(hth1, &ExitCode); //获取线程退出码,因为刚刚创建,所以 //肯定还未退出,此时的退出码应为259,表示STILL_ACTIVE printf("initial thread 1 exit code = %u线程未退出\n", ExitCode); ResumeThread(hth1); // 激活线程 WaitForSingleObject(hth1, INFINITE);//等待线程结束 GetExitCodeThread(hth1, &ExitCode);//获取线程退出码 printf("thread 1 exited with code %u,its pid=%u,its handle=%d\n", ExitCode, Thread1ID,hth1);

Windows环境下的多线程编程

Windows环境下的多线程编程浅讲这是一个简单的关于多线程编程的指南,任何初学者都能在这里找到关于编写简单多线程程序的方法。如果需要更多的关于这方面的知识可以参考MSDN或本文最后列出的参考文献。本文将以C语言作为编程语言,在Lcc-Win32编译器下进行编译调试,使用MS-Visual Studio的人可以参考其相关的使用手册。没有C语言基础的朋友可以找一本简单的教程看一下,相信会是有帮助的。 首先解释什么是线程,谈到这个就不得不说一下什么是进程,这是两个相关的概念。为了方便理解,我推荐大家下载并使用一下flashget(网际快车)这个下载软件,当然迅雷也行,只是不如flashget这么直观了。O.K.言归正传,当我们打开flashget这个软件时,就是启动了一个进程。这个进程就指flashget这个软件的运行状态。当我们用它来下载时都是在整个flashget软件里操作,所以可以把单个进程看作整个程序的最外层。然后我们可以看到,在用flashget下载时可以选择同时使用多少线程来下载的选项(这里介绍个小技巧,用超级兔子可以优化这里的选项,将最大的线程数限制从10改为30),这个选项就包含了我所要讲的线程的概念。当同时用比如5根线程来下载的话,flashget就会同时使用5个独立的下载程序来下载文件(各个线程通过某种方式通信,以确定各自所要下载的部分,关于线程间的通信在后面介绍)。所以线程可以看作是在进程框架下独立运行的与进程功能相关的程序。如果将windows看作进程的话,那么里面跑得QQ啊,MSN什么的都可以看作是windows的线程了。当然进程本身也可以看作是线程,只是凌驾于其它线程之上的线程罢了。另外比如我们使用浩方对战平台来网上对战,当用浩方启动魔兽时,由于运行的是两个不同的程序,那就是多进程编程了,这要比多线程复杂点,但也是复杂的有限,有兴趣的人可以参考MSDN上的相关资料。 最后声明一下,文中的例子都没有过多的注释,不过我都使用了最简单的例子(至少我个人已经想不出更简单的了),如果读者对理解这里的程序上有困难,那么我的建议是这篇文章已经不适合你了,你应该看一些更基础的书,比如《小学生基础算数》^_^。 为了使对多线程编程有个更感性的认识,这里先给出一个简单的多线程程序。

Windows多线程编程

Windows多线程编程 作者:韩耀旭 多线程编程之一——问题提出 (2) 一、问题的提出 (2) 二、多线程概述 (2) 三、Win32API对多线程编程的支持 (3) 四、Win32API多线程编程例程 (4) 例程1MultiThread1 (4) 例程2MultiThread2 (5) 例程3MultiThread3 (6) 例程4MultiThread4 (9) 多线程编程之二——MFC中的多线程开发 (11) 五、MFC对多线程编程的支持 (11) 六、MFC多线程编程实例 (12) 例程5MultiThread5 (12) 例程6MultiThread6 (15) 多线程编程之三——线程间通讯 (19) 七、线程间通讯 (19) 例程7MultiThread7 (19) 多线程编程之四——线程的同步 (23) 八、线程的同步 (23) 例程8MultiThread8 (24) 例程9MultiThread9 (26) 例程10MultiThread10 (29) 注:参考自https://www.wendangku.net/doc/98379287.html,/space-290696-do-blog-id-13861.html

多线程编程之一——问题提出 下载源代码 https://www.wendangku.net/doc/98379287.html,/link.php?url=https://www.wendangku.net/doc/98379287.html,%2Fcode%2Fdowncode.asp%3Fid%3D29 73 一、问题的提出 编写一个耗时的单线程程序: 新建一个基于对话框的应用程序SingleThread,在主对话框IDD_SINGLETHREAD_DIALOG添加一个按钮,ID为IDC_SLEEP_SIX_SECOND,标题为“延时6秒”,添加按钮的响应函数,代码如下: void CSingleThreadDlg::OnSleepSixSecond() { Sleep(6000);//延时6秒 } 编译并运行应用程序,单击“延时6秒”按钮,你就会发现在这6秒期间程序就象“死机”一样,不在响应其它消息。为了更好地处理这种耗时的操作,我们有必要学习——多线程编程。 二、多线程概述 进程和线程都是操作系统的概念。进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成,进程在运行过程中创建的资源随着进程的终止而被销毁,所使用的系统资源在进程终止时被释放或关闭。 线程是进程内部的一个执行单元。系统创建好进程后,实际上就启动执行了该进程的主执行线程,主执行线程以函数地址形式,比如说main或WinMain函数,将程序的启动点提供给Windows系统。主执行线程终止了,进程也就随之终止。 每一个进程至少有一个主执行线程,它无需由用户去主动创建,是由系统自动创建的。用户根据需要在应用程序中创建其它线程,多个线程并发地运行于同一个进程中。一个进程中的所有线程都在该进程的虚拟地址空间中,共同使用这些虚拟地址空间、全局变量和系统资源,所以线程间的通讯非常方便,多线程技术的应用也较为广泛。 多线程可以实现并行处理,避免了某项任务长时间占用CPU时间。要说明的一点是,目前大多数的计算机都是单处理器(CPU)的,为了运行所有这些线程,操作系统为每个独立线程安排一些CPU时间,操作系统以轮换方式向线程提供时间片,这就给人一种假象,好象这些线程都在同时运行。由此可见,如果两个非常活跃的线程为了抢夺对CPU的控制权,在线程切换时会消耗很多的CPU资源,反而会降低系统的性能。这一点在多线程编程时应该注意。 Win32SDK函数支持进行多线程的程序设计,并提供了操作系统原理中的各种同步、互斥和临界区等操作。Visual C++6.0中,使用MFC类库也实现了多线程的程序设计,使得多线程编程更加方便。

Windows的多线程同步实验报告

一、实验目的 在掌握基于消息的windows程序结构和多线程程序设计方法的基础上,设计一个多线程同步的程序。使学生能够从程序设计的角度了解多线程程序设计的方法和在windows系统下多线程同步互斥的机制。 二、实验内容 1.理解Windows程序设计的基本思想,理解基于消息的程序设计方法,能够设计出简单的基于事件的windows程序,完成基本控件的使用 2.结合操作系统中信号量与互斥体的概念,在MFC中找到对应的相关类 3.设计一个多线程同步的程序, 多线程概述 进程和线程都是操作系统的概念。进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成,进程在运行过程中创建的资源随着进程的终止而被销毁,所使用的系统资源在进程终止时被释放或关闭。 线程是进程内部的一个执行单元。系统创建好进程后,实际上就启动执行了该进程的主执行线程,主执行线程以函数地址形式,比如说main或WinMain函数,将程序的启动点提供给Windows系统。主执行线程终止了,进程也就随之终止。 每一个进程至少有一个主执行线程,它无需由用户去主动创建,是由系统自动创建的。用户根据需要在应用程序中创建其它线程,多个线程并发地运行于同一个进程中。一个进程中的所有线程都在该进程的虚拟地址空间中,共同使用这些虚拟地址空间、全局变量和系统资源,所以线程间的通讯非常方便,多线程技术的应用也较为广泛。 多线程可以实现并行处理,避免了某项任务长时间占用CPU时间。要说明的一点是,目前大多数的计算机都是单处理器(CPU)的,为了运行所有这些线程,操作系统为每个独立线程安排一些CPU时间,操作系统以轮换方式向线程提供时间片,这就给人一种假象,好象这些线程都在同时运行。由此可见,如果两个非常活跃的线程为了抢夺对CPU的控制权,在线程切换时会消耗很多的CPU资源,反而会降低系统的性能。这一点在多线程编程时应该注意。 Win32 SDK函数支持进行多线程的程序设计,并提供了操作系统原理中的各种同步、互斥和临界区等操作。Visual C++ 6.0中,使用MFC类库也实现了多线程的程序设计,使得多线程编程更加方便。 VC中提供线程同步的方法: 临界区(CCriticalSection) 事件(CEvent) 互斥量(CMutex)

Windows多线程编程入门讲解

Windows 平台下的多线程编程 线程是进程的一条执行路径,它包含独立的堆栈和 CPU 寄存器状态,每个线程共享所 有的进程资源,包括打开的文件、信号标识及动态分配的内存等。一个进程内的所有线程使 用同一个地址空间,而这些线程的执行由系统调度程序控制,调度程序决定哪个线程可执行 以及什么时候执行线程。线程有优先级别, 优先权较低的线程必须等到优先权较高的线程执 行完后再执行。在多处理器的机器上,调度程序可将多个线程放到不同的处理器上去运行, 这样可使处理器任务平衡,并提高系统的运行效率。 Windows 是一种多任务的操作系统,在 Windows 的一个进程内包含一个或多个线程。 32 位 Windows 环境下的 Win32 API提供了多线程应用程序开发所需要的接口函数,而利用 VC中提供的标准C库也可以开发多线程应用程序, 相应的MFC类库封装了多线程编程的 类,用户在开发时可根据应用程序的需要和特点选择相应的工具。 为了使大家能全面地了解 Windows 多线程编程技术,本文将重点介绍 Win32 API和 MFC 两种方式下如何编制多线程 程序。 多线程编程在 Win32 方式下和MFC 类库支持下的原理是一致的, 进程的主线程在任何 需要的时候都可以创建新的线程。当线程执行完后,自动终止线程? 当进程结束后,所有的 线程都终止。所有活动的线程共享进程的资源,因此,在编程时需要考虑在多个线程访问同 一资源时产生冲突的问题。当一个线程正在访问某进程对象,而另一个线程要改变该对象, 就可能会产生错误的结果,编程时要解决这个冲突。 Win32 API下的多线程编程 Win32 API是 Windows 操作系统内核与应用程序之间的界面, 它将内核提供的功能进行 函数包装,应用程序通过调用相关函数而获得相应的系统功能。为了向应用程序提供多线程 功能,Win32 API函数集中提供了一些处理多线程程序的函数集。直接用Win32 API进行程 序设计具有很多优点: 基于 Win32 的应用程序执行代码小,运行效率高,但是它要求程序员 编写的代码较多,且需要管理所有系统提供给程序的资源。用 Win32 API直接编写程序要求 程序员对 Windows 系统内核有一定的了解,会占用程序员很多时间对系统资源进行管理,

Windows进程与线程

实验一Windows进程与线程 1.实验内容 观察Window进程、线程关键数据结构 实验环境: 主机Windows 7 目标机 Windows Server 2003 SP1 (虚拟机) Windbg 工具 2.理论基础 Windows系统内部有执行体对象和内核对象两种类型,执行体对象由执行体的各种组件,如进程管理器所实现,内核对象由Windows内核实现,其更为基础。Windows中的进程和线程在执行体中以对象的方式实现。 一个进程主要由几个部分组成,其中包括一个私有的虚拟地址空间,一个可执行的程序,一个已打开的句柄列表,一个称为访问令牌的安全环境,一个唯一标识,至少一个执行线程。 NOTS中每个进程有5个数据结构: EPROCESS,KPROCESS,PEB,WIN32KPROCESS,子系统csrss为每个win用户进程创建的进程信息数据结构。 线程是一个进程的内部实体,NTOS中每个线程也有5个数据结构: ETHREAD,KTHREAD,TEB,WIN32THREAD,csrss为每个线程建立的线程信息数据结构 3.实验步骤 搭建好实验环境,配置完成后在目标机里运行实验程序test.exe ,中断,在Windbg中Commad窗口输入!process 0 0,如下图,最后一项为测试程序信息。可知其虚地址为813d1948.

其EPROCESS 如下图

查看其偏移0x000处KPROCESS 结构的具体信息

查看进程环境块PEB 信息

查看其线程ETHREAD 结构信息

查看的ThreadsProcess 为 0x813d1948,也就是进程test.exe 的虚地址 继续查看KTHREAD 的信息,如下 kd> dt_kthread 813db4c0 nt!_KTHREAD +0x000 Header : _DISPATCHER_HEADER +0x010 MutantListHead : _LIST_ENTRY [ 0x813db4d0 - 0x813db4d0 ] +0x018 InitialStack : 0xfa0ec000 +0x01c StackLimit : 0xfa0e9000 +0x020 KernelStack : 0xfa0ebd44 +0x024 ThreadLock : 0 +0x028 ApcState : _KAPC_STATE +0x028 ApcStateFill : [23] "???" +0x03f ApcQueueable : 0x1 '' +0x040 NextProcessor : 0 '' +0x041 DeferredProcessor : 0 ''

相关文档
相关文档 最新文档