Java网络编程以及简单的聊天程序

网络编程技术是互联网技术中的主流编程技术之一,懂的一些基本的操作是非常必要的。这章主要讲解网络编程,UDP和Socket编程,以及使用Socket做一个简单的聊天软件。

全部代码下载:链接

1.网络编程简要概述:

    网络编程实质实质就是两个(或多个)设备(例如计算机)之间的数据传输。而要实现两台计算机通过互联网连接进行数据传输,必输要满足计算机网络的5层协议(物理层,数据链路层,网络层,运输层,应用层);当然有划分可能不同,但现在大家比较容易接受理解的是五层模型。而其中前三层物理层,数据链路层以及网络层,作为java程序员暂时是不能直接对他进行控制的。而本章讲的网络编程是处在运输层和应用层中。对与计算机网络,可以参看我的博客互联网协议入门

  1. 运输层主要有两种协议TCP和UDP,TCP通过握手协议进行可靠的连接,UDP则是不可靠连接。
  2. 应用层主要就是应用层直接和应用程序接口并提供常见的网络应用服务,主要协议有HTTP,FTP,DNS等。
  3. IP地址:用与标记一台计算机的身份证,此去需要注意的是网络地址转换技术NAT的存在,我们的个人计算机基本上都是专用网络IP(192.168..),这个IP只能本地电脑访问。只有当我们有公网IP后,才能使自己的电脑被世界所有连接在互联网中的电脑访问到。
  4. 服务端口:计算机通过不同的服务端口来区分不同网络服务,就像通过进程号区分不同的进程一样;常见服务的端口,HTTP(80),FTP(21).
  5. URL:统一资源定位符。只能定位互联网资源。
URL基本格式:
protocol://hostname:port/resourcename#anchor
protocol:使用的协议,可以是http,ftp,news,telnet等
hostname:主机名
port:端口号,可选
resourcename:资源名,主机上能访问到的目录或文件
anchor:标记,可选,指定文件中的有特定标记的位置
如:
http://localhost:8080/HttpSer/index.html。

下面主要通过UDP和socket编程来讲解网络编程;

2. UDP编程介绍:

  1. 简介:
    UDP(User Datagram Protocol),中文意思是用户数据报协议,方式类似于发短信息,是一种物美价廉的通讯方式,使用该种方式无需建立专用的虚拟连接,由于无需建立专用的连接,所以对于服务器的压力要比TCP小很多。是一种尽可能可靠传输的协议。主要用于视频电话等对信息准确传输性不高追求速度的应用程序。
  2. 主要类的讲解:
    DatagramSocket:
            DatagramSocket类实现“网络连接”,包括客户端网络连接和服务器端网络连接。虽然UDP方式的网络通讯不需要建立专用的网络连接,但是毕竟还是需要发送和接收数据,DatagramSocket实现的就是发送数据时的发射器,以及接收数据时的监听器的角色
    DatagramPacket:
            DatagramPacket类实现对于网络中传输的数据封装,也就是说,该类的对象代表网络中交换的数据。在UDP方式的网络编程中,无论是需要发送的数据还是需要接收的数据,都必须被处理成DatagramPacket类型的对象,该对象中包含发送到的地址、发送到的端口号以及发送的内容等。
  3. 一个简单的udp通信:
    客户端:
  		DatagramSocket ds = null;
			//定义一个UDP来发送数据
			ds = new DatagramSocket();
			//假设发送的数据是个字符串
			String hello = "hello world";
			//定义一个UDP的数据发送包来发送数据,inetSocketAddress表示要接收的地址
			DatagramPacket dp = new DatagramPacket(hello.getBytes()
						,hello.getBytes().length,new InetSocketAddress("127.0.0.1", 9999));
			for(int i=0;i<10;i++) {
    //数据发送
				ds.send(dp);
    //线程睡眠1s
				Thread.sleep(1000);
			}

服务端:

DatagramSocket ds = null;
		//UDP接受端连接
		ds = new DatagramSocket(9999);
		//定义将UDP的数据包接收到什么地方
		byte[] buf = new byte[1024];
		//定义UDP的数据接收包
		DatagramPacket dp = new DatagramPacket(buf, buf.length);
		while(true) {
			//接收数据包
			ds.receive(dp);
  //将数据转换输出:
			String str = new String(dp.getData(),0,dp.getLength());
			System.out.println(str);
		}

3.socket编程介绍:

  1. socket简介:
    Socket是TCP/IP协议的一个十分流行的编程界面,一个Socket由一个IP地址和一个端口号唯一确定。
    而TCP是Tranfer Control Protocol的 简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送 或接收操作。
  2. 主要的类介绍:
    1.建立服务器类:ServerSocket
    可以用服务器需要使用的端口号作为参数来创建服务器对象:
    ServerSocket serverSocket = new ServerSocket(8888)
    这条语句创建了一个服务器对象,这个服务器使用8888号端口。当一个客户端程序建立一个Socket连接,所连接的端口号为8888时,服务器对象server便响应这个连接,并且server.accept()方法会创建一个Socket对象。服务器端便可以利用这个Socket对象与客户进行通讯。
//进行监听,当客户端没数据发送过来时,停在这里;
	Socket s=serverSocket.accept();

2.通信类:Socket
服务器端和客户端可以通过Socket类进行通信:
客户端建立socket类过程:

//建立socket客户端:第一个参数:ip地址;第二个参数:发送的端口,假如没有服务器,会停在这里,然后扔出异常;
	Socket socket=new Socket("192.168.1.105", 8888);

通过socket类可以获得输入输出流进行通信:

//获得socket的输出流
PrintWriter	out=new PrintWriter(socket.getOutputStream());
			//获得socket的输入流
BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
  1. 一个简单的Socket通信:
    客户端:
public static void main(String[] args) {
		Socket socket=null;
		PrintWriter out=null;
		BufferedReader in=null;
		try {
			//建立socket客户端:第一个参数:ip地址;第二个参数:发送的端口,假如没有服务器,会停在这里,然后扔出异常;
			socket=new Socket("192.168.1.105", 8888);
			//获得本机分配的当地socket端口
			System.out.println("当地端口:"+socket.getLocalPort());
			//获得本机分配的当地socket端口
			System.out.println("远程端口:"+socket.getPort());
			//获得socketAddress
			System.out.println("远程adress:"+socket.getRemoteSocketAddress());
			System.out.println("本地adress:"+socket.getLocalSocketAddress());
			//获得inetAddress
			System.out.println("远程inetAddress:"+socket.getInetAddress());
			System.out.println("本地inetAddress:"+socket.getLocalAddress());
			//获得socket的输出流
			out=new PrintWriter(socket.getOutputStream());
			//获得socket的输入流
			in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
			//发送数据
			out.println("peace");
			//刷新才会立即发送
			out.flush();
			  //接收数据
			   String str=null;
			   //此去会一直等待服务端发送一个数据;
		        str=in.readLine();
				System.out.println("收到:"+str);
			
			
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			//关闭连接
			if(socket!=null){
				try {
					socket.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

服务端:

	public static void main(String[] args) {
		ServerSocket serverSocket=null;
		PrintWriter out=null;
		BufferedReader in=null;
		Socket s=null;
		int i=0;
		try {
			//服务器的创建:创建相应的serversocket
			serverSocket=new ServerSocket(8888);
			while(true){
				try {
					//进行监听,当客户端没数据发送过来时,停在这里;
					s=serverSocket.accept();
					//获得本机分配的当地socket端口
					System.out.println("当地端口:"+s.getLocalPort());
					//获得本机分配的当地socket端口
					System.out.println("远程端口:"+s.getPort());
					//获得socketAddress
					System.out.println("远程adress:"+s.getRemoteSocketAddress());
					System.out.println("本地adress:"+s.getLocalSocketAddress());
					//获得inetAddress
					System.out.println("远程inetAddress:"+s.getInetAddress());
					System.out.println("本地inetAddress:"+s.getLocalAddress());
					//获得socket的输出流,关闭socket时,会自动关闭socket的输出输入留
					out=new PrintWriter(new OutputStreamWriter(s.getOutputStream()));
					//获得socket的输出流,关闭socket时,会自动关闭socket的输出输入留
					in=new BufferedReader(new InputStreamReader(s.getInputStream()));
					//将客户端发过来的数据输出:
					String str=null;
					 str=in.readLine();
						System.out.println("收到"+str);
					
					out.println("hello"+i++);
					
					out.flush();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}finally {
					if(s!=null){
						s.close();
					}
				}
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		finally {
			if(serverSocket!=null){
				try {
					serverSocket.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

4.利用socket建立简单聊天程序:

该聊天进程可以实现指定好友聊天,和群聊。只是简单的演示。

  1. 客户端
    线程数:2个,一个主线程用于发送消息,一个线程用于接收服务器发送过来的消息
    单聊实现:通过single标志进行判断。
    退出实现:控制抬输入:quit就会提示退出
    单聊退出实现:当进入单聊后,控制台输入q就退出单聊
  2. 服务端:
    线程数:由客户端决定,当客户端键入名字之后就启动一个线程用于处理客户端的各种请求,以及发送数据。将所有线程放入map集合,键为用户名,值为线程对象;
    单聊实现:当收到客户端的singl标志后使sin为true实现单聊,同时获得指定用户的线程进行发送数据。
    退出实现:当接收到客户端发送的quit时,向客户端发送disconnect断开连接,关闭线程
    单聊退出:将单聊标志置为false就行;
  3. 客户端代码如下:
package com.rlovep.clinet;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
/**
 * 
* @ClassName: TalkClinet
* @Description: 聊天客户端:
* @author peace w_peace@163.com 
* @date 15 Oct 2015 7:21:22 pm
*
 */
public class TalkClinet {
	// 获得控制台输入
	private BufferedReader sysin = null;
	// socket
	private Socket s = null;
	private BufferedReader in = null;
	private PrintWriter out = null;
	// 线程停止flag
	private boolean flag = true;
    //单聊标志
	private final static String single="single";
	
	public static void main(String[] args) {
		// 启动客户端:
		new TalkClinet().start();
	}
	/**
	 * 
	* @Title: start 
	* @Description: 主要作用:启动发送和接收任务  
	* @return:void   
	* @throws 
	* @author peace w_peace@163.com
	 */
  public void  start(){
	 try {
			// 获得系统输入:
			sysin = new BufferedReader(new InputStreamReader(System.in, "utf-8"));
			// 设置名字:
			System.out.println("请输入用户名:");
			String name = sysin.readLine();
			// 建立客户端socket
			s = new Socket("127.0.0.1", 8888);
			// 获得socket输出out
			out = new PrintWriter(s.getOutputStream(), true);
			out.println(name);
			// 获得socket输入 in
			in = new BufferedReader(new InputStreamReader(s.getInputStream()));
			// 建立接收线程;原因获取系统输入会阻塞主线程
			new Thread(new ClinetThread()).start();
			// 发送消息:
			String str = null;
			while (flag && ((str = sysin.readLine()) != null)) {
				//判断是否为单聊标志。如果不是单聊,就群发消息
				if(single.equalsIgnoreCase(str)){
					System.out.println("请输入想跟谁聊 :");
					//获取系统输入:
					if(flag && ((str = sysin.readLine()) != null))
						{
						      //发送单聊标志
						     out.println(single) ;
						}
				}
				//向服务端发送内容:
				out.println(str);
			}
		
	 } catch (UnsupportedEncodingException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	} catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
	
	 finally{
		 //关闭资源
		 if(sysin!=null){
			 try {
				sysin.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		 }
		 if(s!=null){
			 try {
				s.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		 }
	 }
  }
 private class ClinetThread implements Runnable{
	/**
	 * 
	* @Title: recive 
	* @Description: 接收消息,当消息为disconnect时退出,此去需要在按下一次回车用来终止系统输入; 
	* @return:void   
	* @throws 
	* @author peace w_peace@163.com
	 */
	 private void recive(){
		try {
			//接收服务端消息
			String str=in.readLine();
			if(str!=null){
			//如果是结束聊天,就退出线程
			if("disconnect".equals(str)){
				stop();
				System.out.println("回车退出:");
			}
			else 
			{
				//否则显示消息
			  System.out.println(str);	  
			}
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	 }
	 //线程停止方法
	 public void  stop()
	 {
		 flag=false;
	 }
	 //线程主要任务
	@Override
	public void run() {
		while(flag){
			//接收消息函数调用
			recive();
		}
	} 
 }
}

4.服务端代码如下:

package com.rlovep.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;


public class TalkServer {
	//保存聊天线程:
	private Map<String, ServerThread> map = new HashMap<String, ServerThread>();
	
	
    public static void main(String[] args) {
		//启动服务器
		new TalkServer().start();
	}
	/**
	 * 
	* @Title: start 
	* @Description: 为每一个客户端创建一个独立线程 
	* @return:void   
	* @throws 
	* @author peace w_peace@163.com
	 */
	public void start(){
		//服务器servesocket
		ServerSocket serverSocket=null;
		//服务器socket
		Socket socket=null;
		try {		
			//服务器创建
			serverSocket=new ServerSocket(8888);
			//死循环,用于接收更多的客户端
			while(true){
				//聊天线程创建
			socket=serverSocket.accept();
			//为每一个客户端建立一个服务线程。
			ServerThread st=new ServerThread(socket);
			new Thread(st).start();
			}
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		finally{
			//释放资源
			try {
				if(serverSocket!=null){
					serverSocket.close();
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	/**
	 * 
	* @ClassName: ServerThread
	* @Description: 聊天线程
	* @author peace w_peace@163.com 
	* @date 15 Oct 2015 8:31:44 pm
	*
	 */
	private class ServerThread implements Runnable{
		//相应的socket
		private Socket socket=null;
		private BufferedReader in=null;
		private PrintWriter out=null;
		//每个聊天的名字
		private  String name=null;
		//聊天头
		private String meshead=null;
		//停止线程标志位
		private boolean flag=true;
		//单聊标志位:
	    private boolean sin=false;
		/**
		 * 
		* <p>Title:线程构造器 </p>
		* <p>Description:用来创建线程,以及初始化,和将线程加入map </p>
		* @param socket
		* @throws IOException
		 */
		public ServerThread(Socket socket) throws IOException {
			//获得socket
			this.socket=socket;
			//获得输出和输入
			out=new PrintWriter(socket.getOutputStream(),true);
			//获得用户名字
			in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
			name=in.readLine();
			System.out.println(name+"上线了");
			//制作聊天头
			meshead=name+"["+socket.getInetAddress().getHostAddress()+":"+socket.getPort()+"]";
			//将线程加入map:key为名字;value为线程
			map.put(name, this);
			//提醒所有用户
			send(meshead+"上线了");
		}
		/**
		 * 
		* @Title: send 
		* @Description: 发送消息(群发)
		* @param msg 
		* @return:void   
		* @throws 
		* @author peace w_peace@163.com
		 */
		public void send(String msg){
			//迭代群发
			for(ServerThread thread:map.values()){
				thread.out.println(msg);
			}
		}
		/**
		 * 
		* @Title: Receiver 
		* @Description: 接收消息,并转发
		* @throws IOException 
		* @return:void   
		* @author peace w_peace@163.com
		 */
		public void Receiver() throws IOException{
			String str=null;
			ServerThread qq=null;
			//接收消息
			while((str=in.readLine())!=null){
				//如果消息为quit则退出
				if("quit".equalsIgnoreCase(str)){
					stop();
					//给客户端发送关闭链接命令
					out.println("disconnect");
					break;
		     }
		     //判断是否为单聊
				if(sin==false)
				{
				   if(str.equals("single")){
					   //如果为单聊就获得想要跟谁聊
					if((str=in.readLine())!=null){
						 //获得另外一个客户端线程
						 qq=map.get(str);
						if(qq!=null)
							sin=true;
					}
				    }
				   else
				   {
					 //转发消息  群发
						send(meshead+str);	
				   }
				
				}
				//如果是单聊就给对应的客户端发送消息;
				else{
					//如果为q则退出单聊
					if(str.equals("q")){
						sin=false;
						qq.out.println(name+"对话结束");
					}
					//否则发送消息
					else
					   qq.out.println(name+"对你说:"+str);
					}
			
			}
		}
		/**
		 * 
		* @Title: stop 
		* @Description: 停止函数
		* @return:void   
		* @throws 
		* @author peace w_peace@163.com
		 */
		public void stop(){
			flag=false;
			//下线移去码map;
			map.remove(name);
			send(meshead+"已经下线了");
		}
		/**
		 * run方法
		 */
		@Override
		public void run() {
		
			try {
				while(flag){
				    //不停接收消息
					Receiver();
				}
			}
			catch(SocketException e){
				stop();//客户端直接关闭引发的错误;
			}
			catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			finally{
				//释放资源
				try {
					if(socket!=null){
						socket.close();
					}
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		
	}
}

来自一条小鲨鱼wpeace(rlovep.com)

原文地址:https://www.cnblogs.com/onepeace/p/5020150.html