多线程:
0.
多线程的概念:
多线程是这样一种机制,它允许在程序中并发执行多个线程,且每个线程彼此间互相独立。
并发的理解:
多个线程的执行是并发的,也就是在逻辑上“同时”,而不管是否是物理上的“同时”。
1.
实现线程的方式有两种:
1、继承java.lang.Thread,并重写它的run()方法,将线程的执行主体放入其中。
2、实现java.lang.Runnable接口,实现它的run()方法,并将线程的执行主体放入其中。
==>多线程的执行逻辑:
当主线程被挂起时, 其它就绪的线程会根据选择最高优先级的来执行;
当主线程的挂起时间 > 子线程的执行时间时,子线程执行完后回到主线程,等待主线程醒来.
当主线程的挂起时间 < 子线程的执行时间时,主线程挂起时间到的,自动醒来,回到主线程,此时可以判断子线程是否存在,若有,可stop之.
上面两种实现线程的方式在启动时会有所不同。
# ThreadTest tt = new ThreadTest();
# // 启动线程
# tt.start();
# // 创建一个线程实例
# Thread t = new Thread(new RunnableTest());
# // 启动线程
# t.start();
2.
线程状态的具体信息如下:
1. NEW(新建状态、初始化状态):线程对象已经被创建,但是还没有被启动时的状态。这段时间就是在我们调用new命令之后,调用start()方法之前。
2. RUNNABLE(可运行状态、就绪状态):在我们调用了线程的start()方法之后线程所处的状态。
3. BLOCKED(阻塞状态、被中断运行):
4.TERMINATED(死亡状态、终止状态):线程完成执行后的状态。或run()在运行过程中抛出了一个异常,而这个异常没有被程序捕获,导致这个线程异常终止进入TERMINATED状态。
3.
你可以调用 Thread 类的方法 getPriority() 和 setPriority()来存取线程的优先级,线程的优先级界于1(MIN_PRIORITY)和10(MAX_PRIORITY)之间,缺省是5(NORM_PRIORITY)。
4.
同步synchronized
重点理解:
synchronized 方法,用来控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞;方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放;此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态.
也就是说:
线程在执行同步方法时是具有排它性的。当任意一个线程进入到一个对象的任意一个同步方法时,这个对象的所有同步方法都被锁定了,在此期间,其他任何线程都不能访问这个对象的任意一个同步方法,直到这个线程执行完它所调用的同步方法并从中退出,从而导致它释放了该对象的同步锁之后。在一个对象被某个线程锁定之后,其他线程是可以访问这个对象的所有非同步方法的。
在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成员变量的访问。
同步块也一样:
一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
基本格式:
同步方法:
- public void methodAAA()
- {
- synchronized (this) // (1)
- {
- //…..
- }
- }
- public void methodAAA()
- {
- synchronized (this) // (1)
- {
- //…..
- }
- }
- public void methodAAA()
- {
- synchronized (this) // (1)
- {
- //…..
- }
- }
- public void methodAAA()
- {
- synchronized (this) // (1)
- {
- //…..
- }
- }
同步方法:
- public class Thread1 implements Runnable {
- int num=100;
- public synchronized void run() {
- for (int i = 0; i < 5; i++) {
- System.out.println(Thread.currentThread().getName()+ " 's num is " + num--);
- }
- }
- public static void main(String[] args) {
- Thread1 t1 = new Thread1();
- Thread ta = new Thread(t1, "A");
- Thread tb = new Thread(t1, "B");
- ta.start();
- tb.start();
- }
- }
- public class Thread1 implements Runnable {
- int num=100;
- public synchronized void run() {
- for (int i = 0; i < 5; i++) {
- System.out.println(Thread.currentThread().getName()+ " 's num is " + num--);
- }
- }
- public static void main(String[] args) {
- Thread1 t1 = new Thread1();
- Thread ta = new Thread(t1, "A");
- Thread tb = new Thread(t1, "B");
- ta.start();
- tb.start();
- }
- }
- public class Thread1 implements Runnable {
- int num = 100;
- public void run() {
- synchronized (this) {
- for (int i = 0; i < 5; i++) {
- System.out.println(Thread.currentThread().getName()
- + " 's num is " + num--);
- }
- }
- }
- public static void main(String[] args) {
- Thread1 t1 = new Thread1();
- Thread ta = new Thread(t1, "A");
- Thread tb = new Thread(t1, "B");
- ta.start();
- tb.start();
- }
- }
- public class Thread1 implements Runnable {
- int num = 100;
- public void run() {
- synchronized (this) {
- for (int i = 0; i < 5; i++) {
- System.out.println(Thread.currentThread().getName()
- + " 's num is " + num--);
- }
- }
- }
- public static void main(String[] args) {
- Thread1 t1 = new Thread1();
- Thread ta = new Thread(t1, "A");
- Thread tb = new Thread(t1, "B");
- ta.start();
- tb.start();
- }
- }
添加了同步后,结果:
A 's num is 100
A 's num is 99
A 's num is 98
A 's num is 97
A 's num is 96
B 's num is 95
B 's num is 94
B 's num is 93
B 's num is 92
B 's num is 91
若不添加同步:
A 's num is 100
A 's num is 98
A 's num is 97
A 's num is 96
A 's num is 95
B 's num is 99 --此处
B 's num is 94
B 's num is 93
B 's num is 92
B 's num is 91
说明:
则线程A正在处理的中间数据若结果数据(99),将线程B中调用了;A又调用了线程B的中间数据,继续计算.
注意:
在定义接口方法时不能使用synchronized关键字。
构造方法不能使用synchronized关键字,但可以使用下节要讨论的synchronized块来进行同步。
3.
Lock是一个接口,它位于Java 5.0新增的java.utils.concurrent包的子包locks中。实现Lock接口的类具有与synchronized关键字同样的功能,但是它更加强大一些。java.utils.concurrent.locks.ReentrantLock是较常用的实现了Lock接口的类。上面的实例可以变为:
- public class Thread1 implements Runnable {
- int num = 100;
- private Lock lock = new ReentrantLock();
- public void run() {
- try {
- lock.lock();
- for (int i = 0; i < 5; i++) {
- System.out.println(Thread.currentThread().getName()
- + " 's num is " + num--);
- }
- }
- catch (Exception e) {
- e.printStackTrace();
- }
- finally
- {
- lock.unlock();
- }
- }
- public static void main(String[] args) {
- Thread1 t1 = new Thread1();
- Thread ta = new Thread(t1, "A");
- Thread tb = new Thread(t1, "B");
- ta.start();
- tb.start();
- }
- }
- public class Thread1 implements Runnable {
- int num = 100;
- private Lock lock = new ReentrantLock();
- public void run() {
- try {
- lock.lock();
- for (int i = 0; i < 5; i++) {
- System.out.println(Thread.currentThread().getName()
- + " 's num is " + num--);
- }
- }
- catch (Exception e) {
- e.printStackTrace();
- }
- finally
- {
- lock.unlock();
- }
- }
- public static void main(String[] args) {
- Thread1 t1 = new Thread1();
- Thread ta = new Thread(t1, "A");
- Thread tb = new Thread(t1, "B");
- ta.start();
- tb.start();
- }
- }
注意:
引入了锁,但是锁的引入常常会造成一个很大的问题——死锁 。
死锁就是一个进程中的每个线程都在等待这个进程中的其他线程释放所占用的资源,从而导致所有线程都无法继续执行的情况。
4.
线程的阻塞
阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪),Java 提供了大量方法来支持阻塞,下面让我们逐一分析。
1. sleep() 方法:sleep() 允许指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻塞状态,不能得到CPU 时间,指定的时间一过,线程重新进入可执行状态。
2. suspend() 和 resume() 方法:两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume() 被调用,才能使得线程重新进入可执行状态。
3. yield() 方法:yield() 使得线程放弃当前分得的 CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。
4. wait() 和 notify() 方法:两个方法配套使用,wait() 使得线程进入阻塞状态,它有两种形式,一种允许指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的 notify() 被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的 notify () 被调用。
区别:
初看起来 wait() 和 notify()与 suspend() 和 resume() 方法对没有什么分别,但是事实上它们是截然不同的。区别的核心在于,前面叙述的所有方法,阻塞时都不会释放占用的锁(如果占用了的话),而 wait() 和 notify()这一对方法则相反, wait() 方法导致线程阻塞,并且该对象上的锁被释放。
在上面的实例中修改:
- public static void main(String[] args) {
- Thread1 t1 = new Thread1();
- Thread ta = new Thread(t1, "A");
- Thread tb = new Thread(t1, "B");
- ta.start();
- try {
- ta.sleep(10000);
- }
- catch (Exception e) {}
- tb.start();
- }
- public static void main(String[] args) {
- Thread1 t1 = new Thread1();
- Thread ta = new Thread(t1, "A");
- Thread tb = new Thread(t1, "B");
- ta.start();
- try {
- ta.sleep(10000);
- }
- catch (Exception e) {}
- tb.start();
- }
若用wait(),则直接输出A,B信息.
JAVA多线程编程。用JAVA写一个多线程程序,如写四个线程,二个加1,二个对一个变量减一,输出。(须考虑同步)
- //将要加减的变量设置为static;将加减操作及相应的变量定义在另一类中.在线程中进行调用即可.
- //只要是多线程,就要考虑其同步.即要用synchrnized
- class t
- {
- private static int x=10;
- public synchronized void add()
- {
- x++;
- System.out.println("x="+x);
- }
- public synchronized void jian()
- {
- x--;
- System.out.println("x="+x);
- }
- }
- class t1 extends Thread
- {
- public void run()
- {
- t t1=new t();
- t1.add();
- }
- }
- class t2 extends Thread
- {
- public void run()
- {
- t t1=new t();
- t1.jian();
- }
- }
- public class ThreadTest
- {
- public static void main(String [] args)
- {
- t1 t11=new t1();
- t1 t12=new t1();
- t2 t21=new t2();
- t2 t22=new t2();
- t11.start();
- t12.start();
- t21.start();
- t22.start();
- }
- }
- //将要加减的变量设置为static;将加减操作及相应的变量定义在另一类中.在线程中进行调用即可.
- //只要是多线程,就要考虑其同步.即要用synchrnized
- class t
- {
- private static int x=10;
- public synchronized void add()
- {
- x++;
- System.out.println("x="+x);
- }
- public synchronized void jian()
- {
- x--;
- System.out.println("x="+x);
- }
- }
- class t1 extends Thread
- {
- public void run()
- {
- t t1=new t();
- t1.add();
- }
- }
- class t2 extends Thread
- {
- public void run()
- {
- t t1=new t();
- t1.jian();
- }
- }
- public class ThreadTest
- {
- public static void main(String [] args)
- {
- t1 t11=new t1();
- t1 t12=new t1();
- t2 t21=new t2();
- t2 t22=new t2();
- t11.start();
- t12.start();
- t21.start();
- t22.start();
- }
- }
线程池
线程池就像数据库连接池一样,是一个对象池。所有的对象池都有一个共同的目的,那就是为了提高对象的使用率,从而达到提高程序效率的目的.
多线程---守护线程
在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)
Daemon的作用是为其他线程的运行提供便利服务,比如垃圾回收线程就是一个很称职的守护者。
6.
终止线程三种方式:
当run方法完成后线程终止;
使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果);
使用interrupt方法中断线程.
------------------------------------文件读写----------------------------------
I/O操作
对于输入/输出问题,Java将之抽象化为流(Stream)对象来解决。
对输入/输出流,及文件的处理,注意要在最后关闭流或文件,且在处理前后添加try..catch.
Reader、Writer与其子类可以用于进行所谓纯文本文件的字符读/写.
InputStreamReader和OutputStreamWriter (它们分别为Reader和Writer的子类.)
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream);
int ch = 0;
// 以字符方式显示文件内容
while ((ch = inputStreamReader.read()) != -1) {
outputStreamWriter.write(ch);
}
inputStreamReader.close();
outputStreamWriter.close();
FileReader和FileWriter(它们分别继承自InputStreamReader与 OutputStreamWriter)
操作更加简单,不用先建input/outputstream,直接将路径放上去就行.
FileReader fileReader = new FileReader("D://jason.txt");
FileWriter fileWriter = new FileWriter("D://change.txt");
BufferedReader和BufferedWriter(常用于整行读写,单字读写用上面的两类)
读写效率比较高,但须先创建InputStreamReader/OutputStreamWriter或FileReader/FileWriter作为其参数
try {
BufferedReader reader=new BufferedReader(new FileReader("D://jason.log"));
BufferedWriter writer=new BufferedWriter(new FileWriter("D://change.txt"));
String show=reader.readLine(); //读取第一行信息
while(!show.equals("bye"))
{
System.out.println(show);
writer.write(show+"/n"); //其中/n用于换行
show=reader.readLine(); //进行循环读取
}
writer.flush();
reader.close();
writer.close();
} catch (Exception ee) {
ee.printStackTrace();
}
注意:只有BufferedReader或BufferedInputStream,没有BufferedInputStreamReader.
说明:
BufferedReader的readline()方法,实际就是以下的样式的定制:
byte[] a=new byte[2]; --->自己定制缓存的大小,而readline(),则是原先定制好的.
.....
if((ch=A.read(a))!=-1)
{B.wirte(a)}
------------------------------------socket----------------------------------
socket,
对输入/输出流,及文件的处理,注意要在最后关闭流或文件,且在处理前后添加try..catch
我们可以做一个简单的 Socket 例程了 .
服务端 :
- import java.io.*;
- import java.net.*;
- public class MyServer {
- public static void main(String[] args) throws IOException{
- ServerSocket server=new ServerSocket(5678);
- Socket client=server.accept();
- BufferedReader in=new BufferedReader(new InputStreamReader(client.getInputStream()));
- PrintWriter out=new PrintWriter(client.getOutputStream());
- while(true){
- String str=in.readLine();
- System.out.println(str);
- out.println("has receive....");
- out.flush();
- if(str.equals("end"))
- break;
- }
- client.close();
- }
- }
- import java.io.*;
- import java.net.*;
- public class MyServer {
- public static void main(String[] args) throws IOException{
- ServerSocket server=new ServerSocket(5678);
- Socket client=server.accept();
- BufferedReader in=new BufferedReader(new InputStreamReader(client.getInputStream()));
- PrintWriter out=new PrintWriter(client.getOutputStream());
- while(true){
- String str=in.readLine();
- System.out.println(str);
- out.println("has receive....");
- out.flush();
- if(str.equals("end"))
- break;
- }
- client.close();
- }
- }
注意:只有BufferedReader或BufferedInputStream,没有BufferedInputStreamReader.
这个程序的主要目的在于服务器不断接收客户机所写入的信息直到客户机发送 "End" 字符串就退出程序 . 并且服务器也会做出 "Receive" 为回应 . 告知客户机已接收到消息 .
客户机代码 :
- import java.net.*;
- import java.io.*;
- public class Client{
- static Socket server;
- public static void main(String[] args)throws Exception{
- server=new Socket(InetAddress.getLocalHost(),5678);
- BufferedReader in=new BufferedReader(new InputStreamReader(server.getInputStream()));
- PrintWriter out=new PrintWriter(server.getOutputStream());
- BufferedReader wt=new BufferedReader(new InputStreamReader(System.in));
- while(true){
- String str=wt.readLine();
- out.println(str);
- out.flush();
- if(str.equals("end")){
- break;
- }
- System.out.println(in.readLine());
- }
- server.close();
- }
- }
- import java.net.*;
- import java.io.*;
- public class Client{
- static Socket server;
- public static void main(String[] args)throws Exception{
- server=new Socket(InetAddress.getLocalHost(),5678);
- BufferedReader in=new BufferedReader(new InputStreamReader(server.getInputStream()));
- PrintWriter out=new PrintWriter(server.getOutputStream());
- BufferedReader wt=new BufferedReader(new InputStreamReader(System.in));
- while(true){
- String str=wt.readLine();
- out.println(str);
- out.flush();
- if(str.equals("end")){
- break;
- }
- System.out.println(in.readLine());
- }
- server.close();
- }
- }
这个程序只是简单的两台计算机之间的通讯 . 如果是多个客户同时访问一个服务器呢 ? 你可以试着再运行一个客户端 , 结果是会抛出异常的 . 那么多个客户端如何实现呢 ?
如何实现多线程并发socket通讯:
服务端socket,负责监听socket端口,设置一个无限循环,在其中实现:将监听到的socket实例赋给线程且启动线程,由线程来完成业务逻辑.具体如下:
import java.net.*;
import java.io.*;
public class MultiUser extends Thread{
private Socket client;
public MultiUser(Socket c){
this.client=c;
}
public void run(){
try{
BufferedReader in=new BufferedReader(new InputStreamReader(client.getInputStream()));
PrintWriter out=new PrintWriter(client.getOutputStream());
//Mutil User but can't parallel
while(true){
String str=in.readLine();
System.out.println(str);
out.println("has receive....");
out.flush();
if(str.equals("end"))
break;
}
client.close();
}catch(IOException ex){
}finally{
}
}
myserver.java修改如下:
public static void main(String[] args)throws IOException{
ServerSocket server=new ServerSocket(5678);
while(true){
//transfer location change Single User or Multi User
MultiUser mu=new MultiUser(server.accept());
mu.start();
}
}
}
2.
用socket传输文件,实际就是打开文件,将内容读取发送到对方,对方获取保存到文件中。
用socket可以传输对象--ObjectInputStream/ObjectOutputStream
1. 对于普通的传输字符串,用 BufferedInputStream /BufferedOutputStream 及 os.println(), is.readline() 即可。
2. 对于文件的传输,用 FileInputStream /FileOutputStream 及 DataInputStream /DataOutputStream 及 os.write(), is.read() 即可。
3. 对于对象的传输,用 ObjectInputStream /ObjectOutputStream 及 os.writeObject(), is.readObject() 即可 .
- public class FileSender {
- private ServerSocket ss = null;
- public FileSender() {
- }
- public void startSend(String filePath, int port) {
- // socket输出流
- DataOutputStream os = null;
- // 文件输入流
- DataInputStream is = null;
- // 建立socket连接
- Socket socket = null;
- try {
- // 选择进行传输的文件
- File file = new File(filePath);
- // 建立socket监听
- ss = new ServerSocket(port);
- socket = ss.accept();
- os = new DataOutputStream(socket.getOutputStream());
- // 将文件名及长度传给客户端。这里要真正适用所有平台,例如中文名的处理,还需要加工,
- // 具体可以参见Think In Java 4th里有现成的代码。
- os.writeUTF(file.getName());
- os.flush();
- os.writeLong((long) file.length());
- os.flush();
- is = new DataInputStream(new BufferedInputStream(
- new FileInputStream(filePath)));
- // 缓冲区大小
- int bufferSize = 8192;
- // 缓冲区
- byte[] buf = new byte[bufferSize];
- // 传输文件
- while (true) {
- int read = 0;
- if (is != null) {
- read = is.read(buf);
- }
- if (read == -1) {
- break;
- }
- os.write(buf, 0, read);
- }
- os.flush();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- // 关闭所有连接
- try {
- if (os != null)
- os.close();
- } catch (IOException e) {
- }
- try {
- if (is != null)
- is.close();
- } catch (IOException e) {
- }
- try {
- if (socket != null)
- socket.close();
- } catch (IOException e) {
- }
- try {
- if (ss != null)
- ss.close();
- } catch (IOException e) {
- }
- }
- }
- public static void main(String[] args) {
- new FileSender().startSend("E://JDK_API_1_6_zh_CN.CHM", 8821);
- }
- }
- import java.io.BufferedInputStream;
- import java.io.BufferedOutputStream;
- import java.io.DataInputStream;
- import java.io.DataOutputStream;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.net.Socket;
- import java.net.UnknownHostException;
- public class FileIncepter {
- public FileIncepter() {
- }
- public void getFile(String savePath, String ip, int port) {
- // 建立socket连接
- Socket socket = null;
- try {
- socket = new Socket(ip, port);
- } catch (UnknownHostException e1) {
- e1.printStackTrace();
- } catch (IOException e1) {
- e1.printStackTrace();
- }
- // 建立socket输入流
- DataInputStream inputStream = null;
- try {
- inputStream = new DataInputStream(new BufferedInputStream(socket
- .getInputStream()));
- } catch (IOException e1) {
- e1.printStackTrace();
- }
- try {
- // 缓冲区大小
- int bufferSize = 8192;
- // 缓冲区
- byte[] buf = new byte[bufferSize];
- int passedlen = 0;
- long len = 0;
- // 获取文件名称
- savePath += inputStream.readUTF();
- DataOutputStream fileOut = new DataOutputStream(
- new BufferedOutputStream(new BufferedOutputStream(
- new FileOutputStream(savePath))));
- // 获取文件长度
- len = inputStream.readLong();
- System.out.println("文件的长度为:" + len + " KB");
- System.out.println("开始接收文件!");
- // 获取文件
- while (true) {
- int read = 0;
- if (inputStream != null) {
- read = inputStream.read(buf);
- }
- passedlen += read;
- if (read == -1) {
- break;
- }
- System.out.println("文件接收了" + (passedlen * 100 / len) + "%");
- fileOut.write(buf, 0, read);
- }
- System.out.println("接收完成,文件存为" + savePath);
- fileOut.close();
- } catch (Exception e) {
- e.printStackTrace();
- return;
- }
- }
- public static void main(String[] args) {
- new FileIncepter().getFile("F://", "localhost", 8821);
- }
MFC下使用CSocket或者CAsyncSocket进行Socket通信,CSocket继承自 CAsyncSocket。这两者的区别在于,CSocket是同步的Socket,CAsyncSocket则是异步的。使用时,CSocket::Receive()和CSocket::Send()函数会阻塞当前线程,直至操作完成;而 CAsyncSocket::Receive()和CAsyncSocket::Send()函数则不阻塞线程,函数立即返回。所以这两者在使用方式上有所不同。这里探讨一种使用CSocket配合CSocketFile、CArchive和 CThread的多线程C/S模式。
CSocket通过CSocketFile由CArchive管理,可以得到类似iostream方式的流输入输出。这种方式的主要过程有:创建连接、接受数据、发送数据和断开连接。CSocket必须附加在与其一起工作的线程上,不能通过其他线程调用,所以主要通过在线程之间的传递消息和加锁实现线程的通信和同
步。我们将分服务端( Server)和客户端(Client)分别讨论具体实现。
服务端
服务端有一个主界面,其类为CSCEServerDlg,继承自 CDialog,它保存线程池和锁。为了简单起见,后面的类声明中大部分的成员变量访问控制都是public。声明类似如下:
typedef
list<CWinThread*> PThreadList;
class
CSCEServerDlg : public
CDialog
{
...
public:
CCriticalSection m_csThrdList; // 线程池锁
PThreadList m_thrdList; // 线程池
int m_thrdIndex; // 线程计数器
CServerSocket m_socketListen // 监听CSocket
...
};
和CSocket一起工作的线程类为CSerSocketThread,继承自CWinThread。这里使用
CWinThread是因为它可以处理消息,这样便于线程间通信。重载了
CWinThread::InitInstance(),在线程建立时做处理,具体实现在后面会有。声明如下:
class
CSerSocketThread : public
CWinThread
{
DECLARE_DYNCREATE(CSerSocketThread)
public:
CSerSocketThread(void);
~CSerSocketThread(void);
virtual
int
ExitInstance();
virtual
BOOL
InitInstance();
int m_thrdIndex;
CSCEServerDlg* m_pSerDlg; // 主界面指针
SOCKET m_hSocket; // Socket 句柄
CServerSocket m_serverSocket; // 附加在这个线程上的CSocket
CSocketFile* m_socketFile; // CSocketFile
CArchive* m_arIn; // 输入CArchive
CArchive* m_arOut; // 输出CArchive
...
protected:
DECLARE_MESSAGE_MAP()
afx_msg
void
OnCustomMsg(WPARAM wParam,LPARAM lParam);
void
ReceiveData();
void
SendData(const
int WP, const
CString& strMsg);
...
};
我们使用的具体的CSocket类是CServerSocket,继承自CSocket,重载了
CSocket::OnAccept()函数和CSocket::OnReceive()函数。监听端口的CSocket在有Socket
连接时会调用OnAccept()函数处理连接的建立。当已经连接的CSocket接收到数据时,则会调用
OnReceive()函数,这里要注意的是,如果对方发送的数据较大,则有可能被分成多次发送,这种情况
下会多次调用OnReceive()函数,后面会提高如何来处理这个问题。同样的道理,在发送数据时则会调
用OnSend()函数,这里我们不会用到它。
CServerSocket的声明:
class
CServerSocket :
public
CSocket
{
public:
CServerSocket(void);
~CServerSocket(void);
virtual
void
OnAccept(int nErrorCode);
virtual
void
OnReceive(int nErrorCode);
CSCEServerDlg* m_pSerDlg; // 主界面指针
CWinThread* m_pThrd; // 该CSocket所在的线程
};
int m_thrdIndex; // 该CSocket所属线程的index,服务 Socket 为 -1
OnAccept()函数的实现:
void
CServerSocket::OnAccept(int nErrorCode)
{
// TODO: 在此添加专用代码和/或调用基类
if(nErrorCode == 0)
{
// 创建一个连接Socket
CServerSocket connectSocket;
Accept(connectSocket); // connectSocket将和客户端连接
// 创建一个线程
CSerSocketThread* pThread =
(CSerSocketThread*)AfxBeginThread(
RUNTIME_CLASS(CSerSocketThread),
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
// 添加到线程池中
m_pSerDlg->m_csThrdList.Lock(); // 加锁
m_pSerDlg->m_thrdList.push_back(pThread);
m_pSerDlg->m_csThrdList.Unlock(); // 解锁
pThread->m_pSerDlg = m_pSerDlg;
/*
* 将CSocket交给新建的线程
* 这样这个CSocket将和新建的线程一起工作
* 注意现在是在界面线程中,因为是监听CSocket,是CServerDlg的成员变量
* 必须通过Detach()得到HANDLE之后在工作线程中重新Attach()才行
*/
pThread->m_hSocket = connectSocket.Detach();
// 线程编号,通过这个在通信时区别不同的客户端
pThread->m_thrdIndex = m_pSerDlg->m_thrdIndex++;
// 启动线程
pThread->ResumeThread();
}
CSocket::OnAccept(nErrorCode);
}
当监听CSocket监听到有客户端接入,调用OnAccept()函数。通过Accept()函数将连接建立,
之后建立一个新的线程,让连接的CSocket附加上去,将线程加入线程池,OnAccept()函数的主要工
作就完成了。
OnReceive()函数的实现:
void CServerSocket::OnReceive(int nErrorCode)
{
// 通过自定义消息WM_THREAD和消息参数WP_RECV通知线程去读取数据
m_pThrd->PostThreadMessage(WM_THREAD, WP_RECV, 0);
//CSocket::OnReceive(nErrorCode);
}
当接收到数据时,CServerSocket就通知它附加的线程去接收数据,以便进一步处理。
下面看一下CSerSocketThread的几个处理的实现:
InitInstance()函数,这个函数在新线程被构造出来后调用,用来完成一些自定义的新建过程:
BOOL
CSerSocketThread::InitInstance()
{
// TODO: 在此添加专用代码和/或调用基类
if (!AfxSocketInit()) // 初始化CSocket必须调用的
{
return
CWinThread::InitInstance(); // 立刻退出
}
if(m_serverSocket.Attach(m_hSocket)) // 重新Attach之前传入的Socket
{
m_serverSocket.m_pThrd = this; // 告诉CSocket它所附加工作的线程
m_serverSocket.m_thrdIndex = m_thrdIndex;
m_serverSocket.m_pSerDlg = m_pSerDlg;
// 建立CSocketFile,将CSocket附加在上面
m_socketFile = new
CSocketFile(&m_serverSocket);
// 输入CArchive
m_arIn = new
CArchive(m_socketFile, CArchive::load);
// 输出CArchive
m_arOut = new
CArchive(m_socketFile, CArchive::store);
return TRUE; // 这样线程就不会立刻退出
}
}
return
CWinThread::InitInstance(); // 立刻退出
当新线程构造好了之后,InitInstance()函数被调用。将传入的CSocket的HANDLE重新
Attach到线程自己的CSocket成员上,之后建立CSocketFile和两个CArchive用于输入输出,这
里CSocketFile会互斥访问,输入和输出的CArchive无法并发操作它。
之前的CServerSocket::OnReceive()中通过消息通知线程来接受数据,下面看具体的实现。
首先声明将WM_THREAD消息绑定到CSerSocketThread::OnCustomMsg()函数:
BEGIN_MESSAGE_MAP(CSerSocketThread, CWinThread)
ON_THREAD_MESSAGE(WM_THREAD, OnCustomMsg)
...
END_MESSAGE_MAP()
OnCustomMsg()函数的实现:
void CSerSocketThread::OnCustomMsg(WPARAM wParam,LPARAM lParam)
{
switch(wParam)
{
case WP_RECV:
// 接收数据
// 先把接收数据事件关掉
m_serverSocket.AsyncSelect(FD_CLOSE);
*m_arIn >> ...; // 读取数据
// 重新打开接收数据事件
m_serverSocket.AsyncSelect(FD_READ|FD_CLOSE);
...
break;
...
}
}
接收到的WM_THREAD消息包含WP_RECV的消息参数后,首先先关掉接收数据事件,这样就可以通
过CArchive的operator >>一次读取完所有数据(根据自己定义的方式),读取完成后再打开接收
数据事件。通过这个流程,每次数据发送就只会调用一次OnReceive()函数。这里要注意的是必须用
CArchive的operator >>根据发送的情况正确的读取数据,最好都读取出来,否则下次读取时可能会
出现错误。
如果需要发送数据,则在CSerSocketThread线程中使用CArchive的operator <<发送所有
支持
operator <<的类型。注意的是必须是在这个工作线程中调用,CSocket是附加在线程上的,同
样CArchive也只能在工作线程上使用,如果通过别的线程调用,就和系统的Socket表不对应了。另
外还有注意的是输出完后需要显式调用CArchive::Flush()将数据发出。
主界面对CServerSocket的使用则比较简单,如下:
m_socketListen.m_pSerDlg = this;
if (!AfxSocketInit()) // Socket初始化
SomeMessageFunc("SOCKET 初始化失败!");
else
if (!m_socketListen.Create(xxxx)) // 创建监听Socket
SomeMessageFunc("监听 SOCKET 创建失败!");
else
if (!m_socketListen.Listen()) // Socket监听
SomeMessageFunc("监听 SOCKET 监听失败!");
else
SomeMessageFunc("监听建立");
客户端
客户端部分的大部分处理和服务端是类似的,主要区别在于客户端主动连接服务端的监听端口。客
户端首先建立工作线程,在线程重载的InitInstance()函数中建立到服务端的连接,成功就继续工作,
失败就停止线程即可。之后当CSocket接收数据是,调用重载的OnReceive()函数,通过消息通知工
作线程使用CArchive读取数据。发送时则由工作线程使用CArchive发送数据,显式调用
CArchive::Flush()。
连接代码,依然是在:
BOOL
CCliSocketThread::InitInstance()
{
// TODO: 在此添加专用代码和/或调用基类
if (!AfxSocketInit()) // 一样的初始化,必须调用
{
return
CWinThread::InitInstance();
}
if (!m_clientSocket.Create()) // 建立CSocket
{
SomeMessageFunc("Socket 创建失败");
return
CWinThread::InitInstance();
}
m_clientSocket.m_pCliDlg = m_pCliDlg;
CString strIP("xxx.xxx.xxx.xxx");
UINT port = xxxx;
if (m_clientSocket.Connect(strIP, port)) // 连接服务端
{
// 连接建立
m_clientSocket.m_thrd = this;
m_socketFile = new
CSocketFile(&m_clientSocket);
m_arIn = new
CArchive(m_socketFile, CArchive::load);
m_arOut = new
CArchive(m_socketFile, CArchive::store);
SomeMessageFunc("连接成功");
return TRUE; // 线程继续工作
}
return
CWinThread::InitInstance();
}
该结构的示意图如下:
client.cpp
#include <windows.h>
#include <iostream>
#pragma comment(lib, "ws2_32")
#define LEN 1024
#define TRUE 1
#define FALSE_F -1
#define OK 1
#define ERROR_R -1
int main()
{
int i=100;
int ret;
char buf1[LEN];
char buf[LEN];
WORD wdVersionRequired;
WSADATA wsaData;
memset(buf, 0, sizeof(buf));
memset(buf1, 0, sizeof(buf1));
wdVersionRequired = MAKEWORD(2, 2); /* 2.2°汾 */
WSAStartup(wdVersionRequired, &wsaData);
while(i--)
{
SOCKET sock;
sockaddr_in saServer;
sock = socket(AF_INET, SOCK_STREAM, 0);
if (SOCKET_ERROR == sock)
{
return FALSE;
}
memset(&saServer, 0, sizeof(saServer));
saServer.sin_family = AF_INET;
saServer.sin_addr.S_un.S_addr = inet_addr("192.168.2.80");
saServer.sin_port = htons(8066);
if(connect(sock, (const sockaddr*)&saServer, sizeof(sockaddr)) != 0)
{
printf("connect error/n");
}
memset(buf, 0, sizeof(buf));
strcpy(buf, "hello world");
if(0 == send(sock, buf, strlen(buf), 0))
{
printf("send error/n");
break;
}
ret = recv(sock, buf1, sizeof(buf1), 0);
buf1[ret]='/0';
printf("buf1: %s/n/n", buf1);
closesocket(sock);
}
WSACleanup();
return 1;
}
================================================
/*******************************
server.cpp
********************************/
#include <iostream>
#include <winsock2.h>
#pragma comment(lib, "ws2_32")
#define LEN 1024
#define OK 1
#define ERROR_R -1
#define FALSE 0
#define TRUE 1
DWORD WINAPI AnswerThread(LPVOID lparam)
{
int err;
char buf[32]="";
char recvbuf[32]="yes receive!!";
SOCKET ClientSocket=(SOCKET)(LPVOID)lparam;
// memset(recvbuf, 0, sizeof(recvbuf));
memset(buf, 0, sizeof(buf));
err = recv(ClientSocket, buf, sizeof(buf), 0);
send(ClientSocket, recvbuf, strlen(recvbuf), 0);
buf[err]='/0';
printf("revc: %s/n", buf);
closesocket(ClientSocket);
return OK;
}
int main()
{
int err;
SOCKET sock;
SOCKET sClient;
DWORD dwThreadId;
WORD wdVersionRequired;
HANDLE hThread;
WSADATA wsaData;
sockaddr_in saServer, cli_addr;
int len = sizeof(cli_addr);
wdVersionRequired = MAKEWORD(2, 2); //³ÌÐòҪʹÓÃ2.2°æ±¾
//×¢Ò⺯Êý·µ»ØÖµ£¬0±íʾÕý³£
if ((err = WSAStartup(wdVersionRequired, &wsaData)) != 0)// ³õʼ»¯WinSock¿â¡£
return FALSE;
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
printf(" create socket error/n");
}
saServer.sin_family = AF_INET;
saServer.sin_addr.S_un.S_addr = inet_addr("192.168.2.80");
saServer.sin_port = htons(8066);
if ((err = bind(sock, (const sockaddr *)&saServer, sizeof(sockaddr)))!= 0)
{
printf("failed bind/n");
}
if ((err = listen(sock, 100)) != 0)//listen()¼àÌý
{
printf("failed listen/n");
}
while(1)
{
sClient = accept(sock, (struct sockaddr*)&cli_addr, &len);
Sleep(1000);
hThread=CreateThread(NULL,NULL, AnswerThread,(LPVOID)sClient, 0, &dwThreadId);
if(hThread==NULL)
printf("CreatThread AnswerThread() failed./n");
else
printf("CreateThread OK./n");
}
closesocket(sock);
WSACleanup();
return TRUE;
}
线程是进程的一个执行单元,线程之间可以进行通讯,但是线程的执行是无序的。在java 的socket开发的时候,为什么要用线程开发,我的理解是是这样的。
在服务器端,用ServerSocket类的ServerSocket.accept()函数,会产生一个阻塞,知道接收到客户端的一个连接为止,这里是不需要进行线程的。
但是在后面的线程之间的读写过程,因为Server端要不断监控cllient端来的数据,即是Socket.getInputStream,这是一个循环过程,一般用while(true)来解决,用接收到某一个特定条件的字符串来结束,这个时候如果不用线程的话,因为server端的执行是按照顺序执行的,所以其他client的连接只能进行等待。正是因为这个原因,在处理的时候必须要阻塞,所以需要用线程来进行处理,client端也是一个道理。
需要特别注意的是,在socket.getInputStream和socket.getOutputStream的时候不会产生阻塞,但是如果BufferReader br = new BufferReader( new InputStreamReader( socket.getInputStream)); br.readLine()的时候会产生阻塞,这个阻塞在while循环里面停住,一直等到socket的另一端有数据传过来。】
IOCP (I/O Completion Ports),中文名"输入输出完成端口"。
为什么要使用IOCP:
1,如果一个客户端对应开启一个线程,对于系统资源是一种浪费,同时也是一项挑战;
2,线程是内核对象,内核的资源是有限的,PC的最大线程数大概在2000左右,同时开启线程也需要额外的系统开销,时间和效率上不划算。
优点:
1,没有HANDLS数量限制,与WaitForMultipleObjects()的优势点;
2,基于线程池,准许一个线程暂停,由另一线程为其服务;
3,支持scalable架构。
主要的工作有三个:
1,关联一个SOCKET到IOCP
IOCP的创建函数有两个用途:
HANDLE CreateIoCompletionPort (
HANDLE FileHandle, // file handle to associate with // the I/O completion port
HANDLE ExistingCompletionPort, // handle to the I/O completion port
DWORD CompletionKey, // per-file completion key for I/O // completion packets
DWORD NumberOfConcurrentThreads // number of threads allowed to // execute concurrently);
举例:
CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, g_h.nThreadNum);//方式一:用于创建一个IOCP;HANDLE h = CreateIoCompletionPort((HANDLE) socket, hCompletionPort, dwCompletionKey, m_nIOWorkers);//方式二,用于关联一个异步IO对象到IOCP,同时制定CompletionKey参数,和工作线程数量。
注意:第一个参数HANDLE在创建时需要在CreateFile()中制定FILE_FLAG_OVERLAPPED标志。
2,IO的异步调用
BOOL PostQueuedCompletionStatus(
HANDLE CompletionPort, // handle to an I/O completion port
DWORD dwNumberOfBytesTransferred, // value to return via // GetQueuedCompletionStatus' //lpNumberOfBytesTranferred
DWORD dwCompletionKey, // value to return via // GetQueuedCompletionStatus' //lpCompletionKey
LPOVERLAPPED lpOverlapped // value to return via // GetQueuedCompletionStatus'lpOverlapped);
3,线程的同步
BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort, // the I/O completion port of interest
LPDWORD lpNumberOfBytesTransferred, // to receive number of bytes // transferred during I/O
LPDWORD lpCompletionKey, // to receive file's completion key
LPOVERLAPPED *lpOverlapped, // to receive pointer to OVERLAPPED // structure
DWORD dwMilliseconds // optional timeout value);
在SOCKET处理过程中,需要处理的4个棘手的问题:
- The WSAENOBUFS error problem.
- The package reordering problem.
- The access violation problem.
- Asynchronous pending reads and byte chunk package processing problem.
工作步骤:
1,产生一个IOCP; 使用 CreateIoCompletionPort
2,让产生的IOCP句柄和一个SOCKET句柄关联; 每关联一个HANDLE都使用 CreateIoCompletionPort
3,产生一堆线程; 线程的数量根据当前CPU的数量计算: 2 * CPU数量 + 2
4,让每一个线程都在IOCP上等待; GetQueuedCompletionStatus
5,对关联的SOCKET句柄发送overlapped IO请求。
线程池中的所有线程个数 = 目前正在执行的线程 + 被阻塞的线程 + 在IOCP上等待的线程
侯捷 多线程程序设计中 ECHO服务器举例:
程序部分一:在主程序MAIN函数中,
初始化SOCKET,在服务端口侦听;
线程集的创建;
IOCP的初始化,使用CreateIoCompletionPort 的方式一;
在FOR循环体中,接受CLIENT的连接请求,为接受到的每个SOCKET CLIENT绑定IOCP,CreateIoCompletionPort 的方式二;
在绑定每一个CLIENT到IOCP时,需要传递一个DWORD CompletionKey, 该参数为CLIENT信息的一个指针。
DWORD ContentKey* pKey;
程序部分二:在线程函数中,
FOR循环体中,不断查询IO队列的状态: GetQueuedCompletionStatus
根据获取到的CLIENT状态进行相应处理。
关键的信息就是ContentKey,每一个CLIENT都有一个。
同步套接字通信
Socket支持下的网上点对点的通信
服务端实现监听连接,客户端实现发送连接请求,建立连接后进行发送和接收数据的功能
服务器端建立一个socket,设置好本机的ip和监听的端口与socket进行绑定,开始监听连接请求,当接收到连接请求后,发送确认,同客户端建立连接,开始与客户端进行通信。
客户端建立一个socket,设置好服务器端的IP和提供服务的端口,发出连接请求,接收到服务的确认后,尽力连接,开始与服务器进行通信。
服务器端和客户端的连接及它们之间的数据传送均采用同步方式。
Socket
Socket是tcp/ip网络协议接口。内部定义了许多的函数和例程。可以看成是网络通信的一个端点。在网络通信中需要两个主机或两个进程。通过网络传递数据,程序在网络对话的每一端需要一个socket。
Tcp/IP传输层使用协议端口将数据传送给一个主机的特定应用程序,协议端口是一个应用程序的进程地址。传输层模块的网络软件模块要于另一个程序通信,它将使用协议端口,socket是运行在传输层的api,使用socket建立连接发送数据要指定一个端口给它。
Socket:
Stream Socket流套接字 Socket提供双向、有序、无重复的数据流服务,出溜大量的网络数据。
Dgram socket数据包套接字 支持双向数据流,不保证传输的可靠性、有序、无重复。
Row socket 原始套接字 访问底层协议
建立socket 用C#
命名空间:using System.Net;using System.Net.Socket;
构造新的socket对象:socket原型:
Public socket (AddressFamily addressFamily,SocketType sockettype,ProtocolType protocolType)
AddressFamily 用来指定socket解析地址的寻址方案。Inte.Network标示需要ip版本4的地址,Inte.NetworkV6需要ip版本6的地址
SocketType参数指定socket类型Raw支持基础传输协议访问,Stream支持可靠,双向,基于连接的数据流。
ProtocolType表示socket支持的网络协议
定义主机对象:
IPEndPoint类:IPEndPoint构造方法 位置:System.Net
原型:1) public IPEndPoint(IPAddress address,int port) 2)public IPEndPoint(long address,int port) 参数1整型int64如123456,参数2端口int32
主机解析:
利用DNS服务器解析主机,使用Dns.Resolve方法
原型:public static IPHostEntry Resolve(string hostname) 参数:待解析的主机名称,返回IPHostEntry类值,IPHostEntry为Inte.Net主机地址信息提供容器,该容器提供存有IP地址列表,主机名称等。
Dns.GetHostByName获取本地主机名称
原型:public static IPHostEntry GetHostByName(string hostname)
GetHostByAddress
原型:1)public static IPHostEntry GetHostByAddress(IPAddress address) 参数:IP地址 2)public static IPHostEntry GetHostByAddress(string address) IP地址格式化字符串
端口绑定和监听:
同步套接字服务器主机的绑定和端口监听
Socket类的Bind(绑定主机),Listen(监听端口),Accept(接收客户端的连接请求)
Bind:原型:public void Bind(EndPoint LocalEP)参数为主机对象 IPEndPoint
Listen:原型:public void Listen(int backlog) 参数整型数值,挂起队列最大值
accept:原型:public socket accept() 返回为套接字对象
演示程序:
IPAddress myip=IPAddress.Parse(“127.0.0.1”);
IPEndPoint myserver=new IPEndPoint(myip,2020);
Socket sock=new Socket(AddressFamily.Inte.Network,SocketType.Stream,ProtocolType.Tcp);
Sock.Bind(myserver);
Sock.Listen(50);
Socket bbb=sock.Accept();
发送数据:方法1:socket类的send方法二.NetworkStream类Write
send原型:public int Send(byte[] buffer) 字节数组
public int Send(byte[],SocketFlags)原型2说明,SocketFlags成员列表:DontRoute(不使用路由表发送),MaxIOVectorLength(为发送和接收数据的wsabuf结构数量提供标准值)None 不对次调用使用标志) OutOfBand(消息的部分发送或接收)Partial(消息的部分发送或接收) Peek(查看传入的消息)
原型三:public int Send(byte[],int,SocketFlags) 参数二要发送的字节数
原型四:public int Send(byte[],int,int,SocketFlags) 参数二为Byte[]中开始发送的位置
演示:
Socket bbb=sock.Accept();
Byte[] bytes=new Byte[64];
string send="aaaaaaaaaaaa";
bytes=System.Text.Encoding.BigEndianUnicode.GetBytes(send.ToCharArray());
bbb.Send(bytes,bytes.length,0);//将byte数组全部发送
.NetWordStream类的Write方法发送数据
原型:public override void write(byte[] buffer,int offset,int size) 字节数组,开始字节位置,总字节数
Socket bbb=sock.Accept();
.NetWorkStream stre=new NewWorkStream(bbb);
Byte[] ccc=new Byte[512];
string sendmessage="aaaaaaaaaaaaaa";
ccc=System.Text.Encoding.BigEndianUnicode.GetBytes(sendmessage);
stre.Write(ccc,0,ccc.length);
接收数据:Socket类Receive.NetworkStream类Read
Socket类Receive方法
原型:public int Receive(byte[] buffer)
2)public int Receive(byte[],SocketFlags)
3)public int Receive(byte[],int,SocketFlags)
4)public int Receive(byte[],int,int,SocketFlags)
.....
Socket bbb=sock.Accept();
........
Byte[] ccc=new Byte[512];
bbb.Receive(ccc,ccc.Length,0);
string rece=System.Text.Encoding.BigEndianUnicode.GetString(ccc);
richTextBox1.AppendText(rece+"/r/n");
.NetworkStream类的Read方法接收数据
public override int Read(int byte[] buffer,int offset,int size)
演示:bbb=sock.Accept();
.......
.NetworkStream stre=new.NetworkStream(bbb);
Byte[] ccc=new Byte[512];
stre.Read(ccc,0,ccc.Length);
string readMessage=System.Text.Encoding.BigEndianUnicode.GetString(ccc);
线程
线程创建:System.Threading空间下的Thread类的构造方法:
原型:public Thread(ThreadStart start) ThreadStart类型值
Thread thread=new Thread(new ThreadStart(accp));
Private void accp(){}//使用线程操作
线程启动
Thread thread=new Thread(new ThreadStart(accp));
线程暂停与重新启动
启动线程使用Thread.Sleep是当前线程阻塞一段时间Thread.Sleep(Timeout.Infinite)是线程休眠,直到被调用Thread.Interrrupt的另一个线程中断或被Thread.Abort中止。
一个线程不能对另一个调用Sleep,可以使用Thread.Suspend来暂停线程,当线程对自身调用Thread.Suspend将阻塞,直到该线程被另一个线程继续,当一个线程对另一个调用,该调用就成为使另一个线程暂停的非阻塞调用。调用Thread.Resume使另一个线程跳出挂起状态并使该线程继续执行,而与调用Thread.Suspend的次数无关
线程休眠:Thread.Sleep(10000);
线程挂起:Thread thread=new Thread(new ThreadStart(accp));
Thread.start();
Thread.Suspend();
重新启动:Thread thread=new Thread(new ThreadStart(accp));
Thread.start();
Thread.Suspend();
Thread.Resume();
阻塞线程的方法:thread.Join使用一个线程等待另一个线程停止
Thread.Join
Public void Join();
Public void Join(int millisecondsTimeout);毫秒
Public bool Join(TimeSpan timeout);时间间隔类型值
实例:Thread thread=new Thread(new ThreadStart(accp));
Thread.start();
Thread.Join(10000);
线程销毁:
Thread.Abort,Thread.Interrupt
Abort方法引发ThreadAbortException,开始中止此线程的过程,是一个可以由应用程序代码捕获的特殊异常,ResetAbort可以取消Abort请求,可以组织ThreadAbortException终止此线程,线程不一定会立即终止,根本不终止。
对尚未启动的线程调用Abort,则当调用Start时该线程将终止。对已经挂起的线程调用Abort,则该线程将继续,然后终止。对阻塞或正在休眠的线程调用Abort,则该线程被中断,然后终止。
Thread类的Abort方法:
Public void Abort()
Public void Abort(object stateinfo);
演示:
Thread thread=new Thread(new ThreadStart(accp));
Thread.Start();
Thread.Abort();
Thread.Join(10000);
Socket编程原理:
Unix的i/o命令集,模式为开-读/写-关 open write/read close
用户进程进行i/o操作
用户进程调用打开命令,获取文件或设备的使用权,并返回描述文件或设备的整数,以描述用户打开的进程,该进程进行读写操作,传输数据,操作完成,进程关闭,通知os对哪个对象进行了使用。
Unix网络应用编程:BSD的套接字socket,unix的System V 的TLI。
套接字编程的基本概念:
网间进程通信:源于单机系统,每个进程在自己的地址范围内进行运行,保证互相不干扰且协调工作。操作系统为进程之间的通信提供设施:
Unix BSD 管道pipe,命名管道named pipe软中断信号signal
Unix System V 消息message 共享存储区 shared memory 信号量semaphore
以上仅限于本机进程之间通信。
端口:网络上可以被命名和寻址的通信端口,是操作系统可以分配的一种资源,网络通信的最终地址不是主机地址,是可以描述进程的摸中标识符。TCP/IP提出协议端口porotocol port端口,表示通信进程。
进程通过os调用绑定连接端口,而在传输层传输给该端口的数据传入进程中处理,同样在进程的数据需要传给传输层也是通过绑定端口实现。进程对端口的操作相当于对os中的i/o文件进行操作,每一个端口也对应着一个端口号,tcp/ip协议分为tcp和udp,虽然有相同port number的端口,但是互相也不冲突。 端口号的分配有全局分配,本地分配(动态分配),当进程需要访问传输层,os分配给进程一个端口号。全局分配,就是os固定分配的端口,标准的服务器都有固定的全局公认的端口号提供给服务。小于256的可以作为保留端口。
地址:网络通信中的两台机器,可以不再同一个网络,可能间隔(网关,网桥,路由器等),所以可以分为三层寻址
机器在不同的网络则有该网络的特定id
同一个网络中的机器应该有唯一的机器id
一台机器内的进程应该有自己的唯一id
通常主机地址=网络ID+主机ID tcp/ip中使用16位端口号来表示进程。
网络字节顺序,高价先存,tcp和udp都使用16或32整数位的高价存储,在协议的头文件中。
半相关:在网络中一个进程为协议+本地地址+端口号=三元组,也叫半相关,表示半部分。
全相关:两台机器之间通信需要使用相同协议
协议+本地地址+本地端口号+远程地址+远程端口号 五元组 全相关。
顺序:两个连续的报文在网络中可能不会通过相同的路径到达,所以接收的顺序会和发送的顺序不一致。顺序是接收顺序与发送顺序一致。Tcp/ip提供该功能。
差错控制:检查数据差错:检查和CheckSum机制 检查连接差错:双方确认应答机制。
流控制:双方传输数据过程中,保证数据传输速率的机制,保证数据不丢失。
字节流:把传输中的报文当作一个字节序列,不提供任何数据边界。
全双工/半双工:两个方向发送或一个方向发送
缓存/带外数据:字节流服务中,没有报文边界,可以同一时刻读取任意长度的数据。为保证传输正确或流协议控制,需要使用缓存,交互型的应用程序禁用缓存。
数据传送中,希望不通过常规传输方式传送给用户以便及时处理的某一类信息(unix系统的中断键delete,Control-c)、终端流控制符Control-s、Control-q)为带外数据。
客户/服务器模式主动请求方式:
1. 打开通信通道,通知本地主机,在某一个公认地址上接收客户请求
2. 等待客户请求到达端口
3. 接收到重复服务请求,处理请求发送应答信号。接收到并发服务请求。要激活一个新进程处理客户请求,unix系统fork、exec,新进程处理客户请求,不需要对其他请求作出应答,服务完成后,关闭此进程与客户的通信链路。终止
4. 返回第二步,等待另一个客户请求。
5. 关闭服务端
客户方:
1. 打开一通信通道,并连接到服务器所在主机的特定端口。
2. 向服务器发送服务请求报文,等待并接收应答;继续提出请求…….
3. 请求结束以后关闭通信通道并终止。
:
1. 客户与服务器进程的作用非对称,编码不同
2. 服务进程先于客户请求而启动,系统运行,服务进程一致存在,直到正常退出或强迫退出
套接字类型:
TCP/IP的socket
Sock_stream可靠的面对连接数据传输,无差错、无重复发送,安照顺序发送接收,内设流量控制,避免数据流超限,数据为字节流,无长度限制,ftp流套接字。
Sock_DGRAM 无连接的服务,数据包以独立包的形式发送,不提供无措保证,数据可能丢失重复,发送接收的顺序混乱,网络文件系统nfs使用数据报式套接字。
Sock_Ram 接口允许较底层协议,IP,ICMP直接访问,检查新的协议实现或访问现有服务中配置的新设备。
服务端:
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
Thread mythread ;
Socket socket;
// 清理所有正在使用的资源。
protected override void Dispose( bool disposing )
{
try
{
socket.Close();//释放资源
mythread.Abort ( ) ;//中止线程
}
catch{ }
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
public static IPAddress GetServerIP()
{
IPHostEntry ieh=Dns.GetHostByName(Dns.GetHostName());
return ieh.AddressList[0];
}
private void BeginListen()
{
IPAddress ServerIp=GetServerIP();
IPEndPoint iep=new IPEndPoint(ServerIp,8000);
socket=new
Socket(AddressFamily.Inte.Network,SocketType.Stream,ProtocolType.Tcp);
byte[] byteMessage=new byte[100];
this.label1.Text=iep.ToString();
socket.Bind(iep);
// do
while(true)
{
try
{
socket.Listen(5);
Socket newSocket=socket.Accept();
newSocket.Receive(byteMessage);
string sTime = DateTime.Now.ToShortTimeString ( ) ;
string msg=sTime+":"+"Message from:";
msg+=newSocket.RemoteEndPoint.ToString()+Encoding.Default.GetString(byteMessage);
this.listBox1.Items.Add(msg);
}
catch(SocketException ex)
{
this.label1.Text+=ex.ToString();
}
}
// while(byteMessage!=null);
}
//开始监听
private void button1_Click(object sender, System.EventArgs e)
{
try
{
mythread = new Thread(new ThreadStart(BeginListen));
mythread.Start();
}
catch(System.Exception er)
{
MessageBox.Show(er.Message,"完成",MessageBoxButtons.OK,MessageBoxIcon.Stop);
}
}
客户端:
using System.Net;
using System.Net.Sockets;
using System.Text;
private void button1_Click(object sender, System.EventArgs e)
{
BeginSend();
}
private void BeginSend()
{
string ip=this.txtip.Text;
string port=this.txtport.Text;
IPAddress serverIp=IPAddress.Parse(ip);
int serverPort=Convert.ToInt32(port);
IPEndPoint iep=new IPEndPoint(serverIp,serverPort);
byte[] byteMessage;
// do
// {
Socket socket=new Socket(AddressFamily.Inte.Network,SocketType.Stream,ProtocolType.Tcp);
socket.Connect(iep);
byteMessage=Encoding.ASCII.GetBytes(textBox1.Text);
socket.Send(byteMessage);
socket.Shutdown(SocketShutdown.Both);
socket.Close();
// }
// while(byteMessage!=null);
}
基于TCP协议的发送和接收端
TCP协议的接收端
using System.Net.Sockets ; //使用到TcpListen类
using System.Threading ; //使用到线程
using System.IO ; //使用到StreamReader类
int port = 8000; //定义侦听端口号
private Thread thThreadRead; //创建线程,用以侦听端口号,接收信息
private TcpListener tlTcpListen; //侦听端口号
private bool blistener = true; //设定标示位,判断侦听状态
private.NetworkStream nsStream; //创建接收的基本数据流
private StreamReader srRead;
private System.Windows.Forms.StatusBar statusBar1;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.ListBox listBox1; //从网络基础数据流中读取数据
private TcpClient tcClient ;
private void Listen ( )
{
try
{
tlTcpListen = new TcpListener ( port ) ; //以8000端口号来初始化TcpListener实例
tlTcpListen.Start ( ) ; //开始监听
statusBar1.Text = "正在监听..." ;
tcClient = tlTcpListen.AcceptTcpClient ( ) ; //通过TCP连接请求
nsStream = tcClient.GetStream ( ) ; //获取用以发送、接收数据的网络基础数据流
srRead=new StreamReader(nsStream);//以得到的网络基础数据流来初始化StreamReader实例
statusBar1.Text = "已经连接!";
while( blistener ) //循环侦听
{
string sMessage = srRead.ReadLine();//从网络基础数据流中读取一行数据
if ( sMessage == "STOP" ) //判断是否为断开TCP连接控制码
{
tlTcpListen.Stop(); //关闭侦听
nsStream.Close(); //释放资源
srRead.Close();
statusBar1.Text = "连接已经关闭!" ;
thThreadRead.Abort(); //中止线程
return;
}
string sTime = DateTime.Now.ToShortTimeString ( ) ; //获取接收数据时的时间
listBox1.Items.Add ( sTime + " " + sMessage ) ;
}
}
catch ( System.Security.SecurityException )
{
MessageBox.Show ( "侦听失败!" , "错误" ) ;
}
}
//开始监听
private void button1_Click(object sender, System.EventArgs e)
{
thThreadRead = new Thread ( new ThreadStart ( Listen ) );
thThreadRead.Start();//启动线程
button1.Enabled=false;
}
// 清理所有正在使用的资源。
protected override void Dispose( bool disposing )
{
try
{
tlTcpListen.Stop(); //关闭侦听
nsStream.Close();
srRead.Close();//释放资源
thThreadRead.Abort();//中止线程
}
catch{}
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
TCP协议的发送端
using System.Net.Sockets; //使用到TcpListen类
using System.Threading; //使用到线程
using System.IO; //使用到StreamWriter类
using System.Net; //使用IPAddress类、IPHostEntry类等
private StreamWriter swWriter; //用以向网络基础数据流传送数据
private.NetworkStream nsStream; //创建发送数据的网络基础数据流
private TcpClient tcpClient;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.TextBox textBox2;
private System.Windows.Forms.StatusBar statusBar1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2; //通过它实现向远程主机提出TCP连接申请
private bool tcpConnect = false; //定义标识符,用以表示TCP连接是否建立
//连接
private void button1_Click(object sender, System.EventArgs e)
{
IPAddress ipRemote ;
try
{
ipRemote = IPAddress.Parse ( textBox1.Text ) ;
}
catch //判断给定的IP地址的合法性
{
MessageBox.Show ( "输入的IP地址不合法!" , "错误提示!" ) ;
return ;
}
IPHostEntry ipHost ;
try
{
ipHost = Dns.Resolve ( textBox1.Text ) ;
}
catch //判断IP地址对应主机是否在线
{
MessageBox.Show ("远程主机不在线!" , "错误提示!" ) ;
return ;
}
string sHostName = ipHost.HostName ;
try
{
TcpClient tcpClient = new TcpClient(sHostName,8000);//对远程主机的8000端口提出TCP连接申请
nsStream = tcpClient.GetStream();//通过申请,并获取传送数据的网络基础数据流
swWriter = new StreamWriter(nsStream);//使用获取的网络基础数据流来初始化StreamWriter实例
button1.Enabled = false ;
button2.Enabled = true ;
tcpConnect = true ;
statusBar1.Text = "已经连接!" ;
}
catch
{
MessageBox.Show ( "无法和远程主机8000端口建立连接!" , "错误提示!" ) ;
return ;
}
}
//发送
private void button2_Click(object sender, System.EventArgs e)
{
if (textBox2.Text !="")
{
swWriter.WriteLine(textBox2.Text);//刷新当前数据流中的数据
swWriter.Flush();
}
else
{
MessageBox.Show("发送信息不能为空!","错误提示!");
}
}
// 清理所有正在使用的资源。
protected override void Dispose( bool disposing )
{
if ( tcpConnect )
{
swWriter.WriteLine ( "STOP" ) ; //发送控制码
swWriter.Flush (); //刷新当前数据流中的数据
nsStream.Close (); //清除资源
swWriter.Close ();
}
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
异步套接字
BeginAccept
Public IAsyncResult BeginAccept{AsyncCallback callback,object state}
AsyncCallback异步回调方法 object state自定义对象, 返回IasyncResult
Using System;
Namespace mySocket
{
Public class Stateobject
{
Public StateObject(){构造函数逻辑}
}
}à>
Using System;
Using System.Net;
Using System.Net.Sockets;
Using System.Threading;
Using System.Text;
Namespace mysocket
{
Public Class StateObject
{
Public Socket worksocket=null;
Public const int buffersize=1024;
Public byte[] buffer=new byte[buffersize];
Public StringBuilder sb=new StringBuilder();
Public StateObject()
{}
}
}
实现主机绑定和端口监听:
Private IPAddress myIP=IPAddress.Parse(“127.0.0.1”);
Private IPEndPoint MyServer;
Private Socket mySocket;
Private Socket Handler;
Private Static ManualResetEvent myreset =new ManualResetEvent(false);
Try
{
IPHostEntry myhost=new IPHostEntry();
Myhost=dns.gethostbyName(“”);
String IPString =myhost.Addresslist[0].tostring();
Myip=IPAddress.Parse(IPString);
}
Catch{MessageBox.Show(“您输入的IP地址格式不正确,重新输入!”);}
Try
{
MyServer=new IPEndPoint(myIP,Int32.Parse(“Port”));
Mysocket=new Socket(AddressFamily.Inte.Network,SocketType.Stream,Protocol.Tcp);
Mysocket.Bind(Myserver);
Mysocket.Listen(50);
Thread thread=new Thread(new ThreadStart(target));
Thread.Start();
}
Catch(Exception ee){}
线程target
Private void target()
{
While(true)
{
myReset.Reset();
mysocket.BeginAccept(new AsyncCallBack(AcceptCallback),mysocket);
myReset.WaitOne();
}
}
异步回调方法AcceptCallBack
Private void AcceptCallback(IAsyncResault ar)
{
myReset.Set();
Socket Listener=(Socket)ar.AsyncState;
Handler=Listener.EndAccept(ar);
StateObject state=new StateObject();
State.workSocket=handler;
Try
{
Byte[] byteData=System.Text.Encoding.BigEndianUnicode.GetBytes(“通话!”+”/n/r”);
Handler.BeginSend(byuteData,0,byteData.Length,0,new AsyncCallback(SendCallback),handler);
}
Catch(Exception ee)
{MessageBox.Show(ee.Message);}
Thread thread=new Thread(new ThreadStart(begreceive));
Thread.Start();
}
多线程:
每个窗体自己都在不同的线程上面运行,如果需要在窗体之间交互,需要在线程之间交互
当线程sleep,系统就使之退出执行队列,当睡眠结束,系统产生时钟中断,使该线程回到执行队列中,回复线程的执行。
如果父线程先于子线程结束,那么子线程在父线程结束的时候被迫结束,Thread.Join()是父线程等待子线程结束。Abort带来的是不可回复的终止线程
起始线程为主线程,前台线程全部结束,则主线程可以终止,后台线程无条件终止。
前台线程不妨碍程序终止,一旦进程的所有前台线程终止,则clr调用任意一个还存活的后台线程的abort来彻底终止进程。
挂起,睡眠(阻塞,暂停)
Thread.Suspend不会使线程立即停止执行,直到线程到达安全点的时候它才可以将该线程挂起,如果线程尚未运行或这已经停止,则不能被挂起,调用thread.resume使另一个线程跳出挂起状态,继续执行。
一个线程不能对另一个线程调用sleep,但是可以suspend。
Lock可以把一段代码定义为互斥段critical section 互斥段在一个时刻内只允许一个线程进入执行,其他线程必须等待
多线程公用对象,不应该使用lock,monitor提供了线程共享资源的方案,monitor可以锁定一个对象,线程只有得到锁才可以对该对象进行操作
一个进程开始至少有一个主线程。系统加载程序时创建主执行线程
消息队列与线程相关
一开始创建线程就产生消息队列了,一个线程可以创建多个窗体,而发给这些窗体的消息都同意发送到同一个消息队列中了,消息结构中有msg.hwnd指出该条消息与哪个窗体相关
DispatchMessage()函数依照这个保证消息分派处理自动化而且不会出错。
线程控制方法:
Start线程开始运行
Sleep 是线程暂停一段指定时间
Suspend 线程在到达安全点后暂停
Abort 线程到达安全点后停止
Resume 重新启动挂起的线程
Join 当前线程等待其他线程运行结束。如果使用超时值,且线程在分配的时间内结束,方法返回true
安全点:代码中的某些位置,这些位置公共语言运行时可以安全的执行自动垃圾回收,即释放未使用的变量并回收内存,调用线程的abort和suspend方法时,公共语言运行时将分析代码并确定线程停止运行的适当位置。
相关推荐
在Python等语言中,可以使用事件驱动编程模型(如asyncio库)或回调函数来实现异步socket。Java则提供了NIO(Non-blocking I/O)框架,通过选择器(Selector)监控多个socket的读写事件,避免了频繁的上下文切换。 ...
本主题将深入探讨同步异步多线程Socket通信,这涉及到如何在多个线程中处理I/O操作,以及同步与异步模型的选择对性能和程序设计的影响。 **同步与异步** 同步和异步是两种处理I/O操作的不同方式。在同步模式下,...
Socket多线程编程是网络编程中的一个重要概念,它结合了Socket通信与多线程技术,以提高程序的并发处理能力。在大型系统中,当需要同时处理多个客户端连接请求时,多线程Socket编程就显得尤为关键。下面将详细介绍...
在IT行业中,多线程和Socket异步编程是两个至关重要的概念,特别是在开发高效、响应迅速的网络应用程序时。本文将深入探讨这两个主题,并结合给定的"C#多线程和Socket异步编程源代码",阐述它们在实际项目中的应用。...
### C#.NET同步异步SOCKET通讯和多线程总结 #### 一、C#.NET Socket编程概述 在C#.NET框架中,网络通信的核心组件是`System.Net.Sockets`命名空间下的`Socket`类,它提供了对底层网络通信的直接控制,适用于需要...
在VC++编程环境中,异步多线程Socket通信是一个关键的技术点,它涉及到网络编程、并发处理以及事件驱动模型等多个方面。在这个项目中,我们有服务端和客户端两部分,两者都利用了异步和多线程技术来提高通信效率和...
在深入学习这部分内容时,你需要理解TCP连接的生命周期、Socket API的使用、多线程或多进程模型在同步和异步编程中的应用,以及如何设计和实现高效的并发处理策略。同时,了解网络编程中的错误处理、异常处理和性能...
4. **第九章多线程编程**: 这个章节可能详细介绍了如何在MFC中设计和管理多线程,包括线程的生命周期管理、异常处理、线程同步策略、线程间通信以及多线程环境下Socket编程的实践案例。 综上所述,MFC多线程...
3. **异步编程模型**:学习C#的Begin/End方法或者基于事件的异步模式(EAP)和基于任务的异步模式(TAP)。 4. **文件传输**:实现文件的分块发送和接收,可能需要结合流(Stream)类。 5. **DataTable操作**:掌握...
这个类可能是为了简化传统的同步Socket编程模型,提高程序的响应性和效率,尤其是在处理大量并发连接时。 首先,让我们了解一下什么是Socket。Socket是操作系统提供的一个抽象接口,允许应用程序通过网络进行数据...
本篇将详细探讨C#中的Socket同步和异步编程模型,以及如何通过实例来理解这两种模式。 首先,我们要明白同步与异步的区别。同步编程模型中,代码会按照顺序执行,发送数据请求后,程序会阻塞等待响应,直到数据接收...
标签中提到的`C#多线程编程`,指的是使用C#进行多线程开发。在Socket异步编程中,多线程用于处理并发的网络请求,例如,一个线程负责接收新的连接,其他线程负责处理这些连接的数据收发。`thread`和`多线程`这两个...
- 在多线程环境下,需要正确管理和同步对Socket对象的访问。 通过学习和实践这个C# Socket同步,异步编程实例,你可以更好地掌握网络编程的基本技巧,并能灵活应用于实际项目,提高程序的效率和用户体验。
通过以上讲解,我们可以理解如何在VC++环境下,使用C++的Socket和多线程功能实现C/S架构的双向通信。实际编程时,需要根据具体需求进行设计和优化,例如,可以考虑使用线程池管理并发连接,或者使用非阻塞I/O或异步I...
IOCP是一种高效的多线程并发处理模型,它可以为多个socket提供统一的事件通知,确保了高并发下的性能。 C++实现一对多异步socket通信,意味着一个服务器端可以同时处理多个客户端的连接请求。服务器端创建一个监听...
在IT领域,多线程和网络编程是两个关键的概念,它们在构建高性能、高并发的系统中发挥着至关重要的作用。...同时,关注最新的技术趋势,如微服务架构、异步编程模型和容器化部署,将有助于你在这个领域保持领先。
本项目"c# socket tcp 多线程 异步聊天 委托 服务 客户端"聚焦于C#语言实现TCP协议的Socket通信,结合多线程和异步编程,以及委托的概念,构建了一个高效的聊天应用实例。以下将详细介绍这些知识点。 首先,Socket...
了解并掌握这些知识点后,你就能构建出一个能够高效处理并发连接的QT Socket多线程服务器。通过实践和调试,你将进一步提升对网络编程和多线程的理解,为开发复杂、高性能的网络应用打下坚实基础。
然而,这也意味着异步Socket的编程模型更为复杂,需要处理多个异步操作的回调和状态管理。 首先,理解异步Socket的工作原理至关重要。以接收数据为例,程序通过调用`BeginReceive`方法启动一个异步接收操作,指定一...
异步Socket编程是一种在客户端和服务器之间进行通信的方法,它允许程序在不阻塞主线程的情况下处理网络数据传输。在传统的同步Socket编程中,当一个Socket进行读写操作时,程序会等待操作完成才会继续执行下一行代码...