网络编程

Java网络编程之TCP/IP


1.1 TCP/IP
协议介绍
 
TCP/IP是Transmission Control Protocol/Internet Protocol的简写,中文译为传输控制协议/因特网互联协议,又叫做网络通讯协议,
由网络层的IP协议和传输层的TCP协议组成,是Internet最基本的协议、Internet国际互联网络的基础,其主要协议如图

网络编程


java TCP/IP编程

服务器端:

public class Server {
	private ServerSocket server;
	/**
	 * 构造方法,用来初始化参数
	 */
	public Server(){
		try {
			/*
			 * 向系统申请2222端口,
			 * 外界可以通过网络发送过来的数据
			 */
			server=new ServerSocket(2222);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	/**
	 * 启动服务器
	 */
	public void start(){
		try {
			System.out.println("info: 等待客户端链接......");
			Socket socket=server.accept();//io堵塞
			System.out.println("info: 客户端"+socket.getLocalAddress().toString()+" 链接上服务器......");
			/**
			 * 获取客户端的输出流,用于获取从客户端
			 */
			BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8"));
			String message=null;
			while((message=in.readLine())!=null){
				System.out.println("客户端 :"+message);
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) {
		Server server=new Server();
		server.start();
	}
}

 

server=new ServerSocket(2222);//服务器端,监听2222端口,
Socket socket=server.accept();//io堵塞 ,等待接受客户端的请求,返回的是客户端的Socket对象,通过Socket对象实现双向通信

客户端:

package com.jsd.jsd1408.chat;

import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

/**
 * @Description: 网络编程案例-客户端
 * @author king-pan
 * @date 2014年10月9日
 * @version V1.0
 */
/*
 * socket 通常称作"套接字",用来描述ip地址和端口,是一个通信链的句柄
 * 
 * 在internet上的主机一般运行了多个服务软件,同时提供几种服务。
 * 
 * 每种服务都打开一个socket,并绑定到一个端口上,
 * 
 * 不同的端口对应于不同的服务。
 */
public class Client {
	/*
	 * 客户端运行的socket,用于链接服务器
	 */
	private Socket socket;
	/**
	 * 启动客户端
	 */
	public Client(){
		try {
			socket=new Socket("localhost", 2222);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	/**
	 * 启动客户端
	 */
	public void start(){
		try {
			/**
			 * 在客户端,若我们希望向服务器发送数据
			 * 我们就通过客户端的Socket获取输出流
			 * 然后向输出流写出数据就行了
			 */
			//字节流太麻烦
			OutputStreamWriter osw=new OutputStreamWriter(socket.getOutputStream(),"UTF-8");
			PrintWriter out=new PrintWriter(osw,false);
			Scanner scanner=new Scanner(System.in);
			String message=null;
			while((message=scanner.nextLine())!=null){
				out.print(message+"\r\n");
				out.flush();
				System.out.println("客户端: 发送消息成功");
			}
			out.close();
			scanner.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) {
		Client client=new Client();
		client.start();
	}
}

socket=new Socket("localhost", 2222);//连接指定ip:port 的服务器
然后通过socket的输出,输入流实现双向通信


改进代码:

服务器端:

package com.jsd.jsd1408.chat;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ServerForm {
	private ServerSocket server;
	/*
	 * 连接池,用来控制和管理与客户端交互的线程
	 */
	private ExecutorService threadPool;
	/*
	 * 该集合用来保存当前服务器中所有客户端的输出流 用来实现广播效果。
	 */
	private List<PrintWriter> allOut;

	/**
	 * 构造方法,用来初始化参数
	 */
	public ServerForm() {
		try {
			/*
			 * 初始化用来存放所有客户端输出流的集合
			 */
			allOut = new ArrayList<PrintWriter>();
			allOut = Collections.synchronizedList(allOut);
			/*
			 * 向系统申请2222端口, 外界可以通过网络发送过来的数据
			 */
			server = new ServerSocket(2222);
			/*
			 * 初始化线程池
			 */
			threadPool = Executors.newFixedThreadPool(50);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	/*
	 * 下面三个方法:
	 * 1:向集合中添加输出流
	 * 2:从集合中删除输出流
	 * 3:遍历集合
	 * 这三个操作既要做到分别同步,又要做到三者互斥
	 * 这是因为多线程需要对这个集合做这三个操作。对于
	 * 线程安全的集合而言,自身是可以做到add,remove
	 * 互斥的。并且保证方法同步。但是无法做到与遍历进行
	 * 互斥。所以我们需要自己来维护三个操作的互斥关系。
	 */

	/**
	 * 将给定的输出流存入共享集合allOut
	 * 
	 * @param pw
	 */
	public synchronized void addOut(PrintWriter pw) {
		// 将给定的流存入集合
		allOut.add(pw);
	}

	/**
	 * 将给定的输出流从共享集合中删除
	 * 
	 * @param pw
	 */
	public synchronized void removeOut(PrintWriter pw) {
		// 将给定的流从集合中删除
		allOut.remove(pw);
	}

	/**
	 * 将给定的消息发送给每一个客户端
	 * 
	 * @param message
	 */
	public synchronized void sendMessageToAllClient(String message) {
		/*
		 * 遍历集合,将给定的字符串发送给每一个输出流 从而达到广播消息的效果
		 */
		for (PrintWriter pw : allOut) {
			pw.println(message);
		}
	}

	/**
	 * 启动服务器
	 */
	public void start() {
		try {
			while (true) {
				System.out.println("info: 等待客户端链接......");
				Socket socket = server.accept();// io堵塞
				System.out
						.println("info: 客户端"
								+ socket.getLocalAddress().toString()
								+ " 链接上服务器......");
				/*
				 * 当一个客户端链接后,我们启动一个线程, 并将该客户端的Socket传入到线程里,使得
				 * 让该线程来完成与当前客户端的交互工作。 这样我们就可以再次调用accept方法等待 其他客户端的链接了。
				 */
				GetClientHandler handler = new GetClientHandler(socket);
				// Thread t = new Thread(handler);
				// t.start();
				/*
				 * 将任务交给线程池
				 */
				threadPool.execute(handler);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		Server server = new Server();
		server.start();
	}

	/**
	 * 该线程体的作用是针对给定的客户端进行交互操作 实际工作用于接受客户端的消息并做处理
	 * 
	 * @author Administrator
	 *
	 */
	class GetClientHandler implements Runnable {
		/*
		 * 该线程用于交互的客户端的Socket
		 */
		private Socket socket;

		public GetClientHandler(Socket socket) {
			this.socket = socket;
		}

		public void run() {
			PrintWriter pw = null;
			try {
				/*
				 * 通过Socket获取输出流,用于将消息 发送给客户端
				 */
				OutputStream out = socket.getOutputStream();
				OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");
				pw = new PrintWriter(osw, true);
				/*
				 * 将当前线程交互的客户端的输出流存入 共享集合,以便于可以广播消息给当前 客户端
				 */
				addOut(pw);

				/*
				 * 通过Socket获取输入流 然后将输入流转换为缓冲字符输入流 循环读取该客户端发送过来的消息
				 */
				InputStream in = socket.getInputStream();
				InputStreamReader isr = new InputStreamReader(in, "UTF-8");
				BufferedReader br = new BufferedReader(isr);

				String message = null;
				/*
				 * 这里判断读取的内容是不是null是出于对linux 与windows底层实现细节的差异 当客户端与服务端断开链接后
				 * 若客户端是linux用户,那么服务端的br.readLine() 方法会返回null,返回null对于缓冲流而言也是表示再
				 * 没有消息可以读取了。所以就应当停止读取工作了。 但是windows的客户端若断开链接,则br.readLine()
				 * 方法会直接抛出异常。
				 */
				while ((message = br.readLine()) != null) {
					// System.out.println("客户端说:"+message);
					// 将消息发送至客户端
					// pw.println(message);
					/*
					 * 当该客户端发送一条消息过来后,我们将该 消息转发给所有客户端,达到广播的效果
					 */
					sendMessageToAllClient(message);
				}

			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				try {
					/*
					 * 当客户端与服务端断开连接后,先将 该客户端的输出流从共享集合中删除 停止对该客户端广播消息
					 */
					removeOut(pw);
					/*
					 * 当客户端与服务端断开链接后 服务端这边也应当将Socket关闭 以释放资源
					 */
					if (socket != null) {
						socket.close();
					}
				} catch (Exception e2) {
					e2.printStackTrace();
				}
			}

		}

	}
}

   服务器端,循环接受客户端请求,把每个客户端请求交给一个线程,然后让线程去负责该客户端。


  


客户端:

package com.jsd.jsd1408.chat;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

/**
 * @Description: 网络编程案例-客户端
 * @author king-pan
 * @date 2014年10月9日
 * @version V1.0
 */
/*
 * socket 通常称作"套接字",用来描述ip地址和端口,是一个通信链的句柄
 * 
 * 在internet上的主机一般运行了多个服务软件,同时提供几种服务。
 * 
 * 每种服务都打开一个socket,并绑定到一个端口上,
 * 
 * 不同的端口对应于不同的服务。
 */
public class ClientForm {
	/*
	 * 客户端运行的socket,用于链接服务器
	 */
	private Socket socket;

	/**
	 * 启动客户端
	 */
	public ClientForm() {
		try {
			socket = new Socket("localhost", 2222);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 客户端开始工作的方法
	 */
	public void start() {
		try {
			/*
			 * 启动客户端中用来读取服务端发送消息的线程
			 */
			GetServerMessageHandler handler = new GetServerMessageHandler();
			Thread t = new Thread(handler);
			t.start();

			/*
			 * 在客户端,若我们希望向服务端发送数据 我们就通过客户端这边的Socket获取 输出流,然后向输出流写出数据就可以了
			 */
			OutputStream out = socket.getOutputStream();

			OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");

			PrintWriter pw = new PrintWriter(osw, true);

			// 创建一个Scanner用于获取用户输入
			Scanner scanner = new Scanner(System.in);
			System.out.println("信息提示 :   请输入聊天昵称 ......");
			pw.println(scanner.nextLine());
			/*
			 * 将字符串发送至服务端
			 */
			while (true) {
				pw.println(scanner.nextLine());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		ClientForm client = new ClientForm();
		client.start();
	}

	/**
	 * 该线程的作用是用于接收服务端发送过来的信息 并输出到客户端的控制台上
	 * 
	 * @author Administrator
	 *
	 */
	class GetServerMessageHandler implements Runnable {

		public void run() {
			try {
				/*
				 * 1.通过Socket获取输入流 2.转换为缓冲字符输入流 3.循环读取服务端发送的消息并输出到 控制台
				 */
				InputStream in = socket.getInputStream();
				InputStreamReader isr = new InputStreamReader(in, "UTF-8");
				BufferedReader br = new BufferedReader(isr);
				String message = null;
				while ((message = br.readLine()) != null) {
					System.out.println(message);
				}

			} catch (Exception e) {
				e.printStackTrace();
			}

		}

	}
}

  客户端连接上服务器,然后与服务器交互

  需要注意的是,使用PrintWrite 的println(),print(),write()方法,print(),write(); 需要在输出的内容后面加上"\r\n"表示该行结束。




你可能感兴趣的:(java基础,socket,TCP/IP)