[Java学习笔记]Socket网络编程——附控制台版群聊、私聊系统

两类传输协议:TCP;UDP

TCP是Tranfer Control Protocol的 简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建 立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送 或接收操作。

UDP是User Datagram Protocol的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。

比较:

UDP:

  • 1,每个数据报中都给出了完整的地址信息,因此无需要建立发送方和接收方的连接。
  • 2,UDP传输数据时是有大小限制的,每个被传输的数据报必须限定在64KB之内。
  • 3,UDP是一个不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方

TCP:

  • 1,面向连接的协议,在socket之间进行数据传输之前必然要建立连接,所以在TCP中需要连接时间。
  • 2,TCP传输数据大小限制,一旦连接建立起来,双方的socket就可以按统一的格式传输大的
    数据。
  • 3,TCP是一个可靠的协议,它确保接收方完全正确地获取发送方所发送的全部数据。

应用:

1,TCP在网络通信上有极强的生命力,例如远程连接(Telnet)和文件传输(FTP)都需要不定长度的数据被可靠地传输。但是可靠的传输是要付出代价的,对数据内容正确性的检验必然占用计算机的处理时间和网络的带宽,因此TCP传输的效率不如UDP高。
2,UDP操作简单,而且仅需要较少的监护,因此通常用于局域网高可靠性的分散系统中client/server应用程序。例如视频会议系统,并不要求音频视频数据绝对的正确,只要保证连贯性就可以了,这种情况下显然使用UDP会更合理一些。

网络套接字

网络套接字是IP地址与端口的组合。所谓套接字,实际上是一个通信端点,每个套接字都有一个套接字序号,包括主机的IP地址与一个16位的主机端口号,即形如(主机IP地址:端口号)

Java中的套接字

  • ServerSocket类
    此类实现服务器套接字。服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。

  • Socket类
    此类实现客户端套接字。

ServerSocket

构造函数:

  • ServerSocket()
    创建非绑定服务器套接字。
  • ServerSocket(int port)
    创建绑定到特定端口的服务器套接字。

主要API:

  • int getLocalPort()
    返回此套接字在其上侦听的端口。
  • Socket accept()
    侦听并接受到此套接字的连接。(这是个阻塞方法,此方法在连接传入之前一直阻塞)

Socket

构造函数:

  • Socket()
    通过系统默认类型的 SocketImpl 创建未连接套接字
  • Socket(InetAddress address, int port)
    创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
  • Socket(String host, int port)
    创建一个流套接字并将其连接到指定主机上的指定端口号。

主要API:

  • InputStream getInputStream()
    返回此套接字的输入流。
  • OutputStream getOutputStream()
    返回此套接字的输出流。
  • void shutdownInput()
    此套接字的输入流置于“流的末尾”。
  • void shutdownOutput()
    禁用此套接字的输出流。

Socket通讯的过程

Server端Listen(监听)某个端口是否有连接请求,Client端向Server 端发出Connect(连接)请求,
Server端向Client端发回Accept(接受)消息。一个连接就建立起来了。Server端和Client 端都可以通过Send,Write等方法与对方通信。

对于一个功能齐全的Socket,都要包含以下基本结构,其工作过程包含以下四个基本的步骤:

(1) 创建Socket;

(2) 打开连接到Socket的输入/出流;

(3) 按照一定的协议对Socket进行读/写操作;

(4) 关闭Socket.(在实际应用中,并未使用到显示的close,虽然很多文章都推荐如此,不过在我的程序中,可能因为程序本身比较简单,要求不高,所以并未造成什么影响。)

群聊系统

要求能够收发信息独立进行(收发分别各为线程)

public class Client {
	private Socket s = null;
	private String Id = null;
	private BufferedReader br = null;
	private PrintWriter pw = null;
	public Client() {
		try {
			s = new Socket("localhost",8080);
			pw = new PrintWriter(s.getOutputStream(),true);
			br = new BufferedReader(new InputStreamReader(s.getInputStream()));
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void send() {
		System.out.print("客户端已开启");
		System.out.println(s);
		Scanner sc = new Scanner(System.in);
		String line = null;
		System.out.println("请输入用户名:");
		Id = sc.nextLine();
		pw.println(Id);
		while((line = sc.nextLine())!= null) 
		{
			if(line.equalsIgnoreCase("exit"))
				break;
			pw.println(line);
		}
		sc.close(); 
	}
	
	public void receive() {
		Thread t = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					while( true) 
						System.out.println(br.readLine());
					
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
		});
		t.start();
	}
	
	public static void main(String[] args) {
		Client c = new Client();
		c.receive();
		c.send();
	}
}

服务器端,监听成功即创建线程任务

public class Server {
	private ServerSocket ss = null;
	static Map<String, Socket> map = new HashMap<>();
	public Server(ServerSocket ss) {
		this.ss = ss;
		System.out.println("服务器已启动");
	}
	public void listen() {
		Socket client = null;
		try {
			while(true) {
				client = ss.accept();
				BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
				String newconnectId = br.readLine();
				System.out.println(newconnectId+"上线了-"+client.getInetAddress());
				map.put(newconnectId, client);
				ServerThread t = new ServerThread(client);
				t.setUserId(newconnectId);
				t.start();
				System.out.println("当前连入服务器的用户有:"+map.size()+"个");
			} 
			
				
			
		}catch (IOException e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) {
		Server s = null;
		try {
			s = new Server(new ServerSocket(8080));
			s.listen();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

群发线程任务,服务器每监听到一个客户端即开启一个该线程任务,它负责监听到客户端后立即发送给所有已监听到的客户端

public class ServerThread extends Thread{
	private String UserId;
	private Socket client = null;
	public ServerThread(Socket s) {
		this.client = s;
	}
	public void setUserId(String name) {
		this.UserId = name;
	}
	
	@Override
	public void run() {
		try {
			BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
			PrintWriter pw = null;
			while(true) {
				String line = br.readLine();
				if(line == null || line.equalsIgnoreCase("exit"))
					break;
				System.out.println("接收到有人发消息,开始群发");
				Collection<Socket> c = Server.map.values();
				for(Socket s : c) {
					pw = new PrintWriter(s.getOutputStream(), true);
					pw.println(this.UserId+": "+line);
				}
			}
			if(pw != null)
				pw.close();
			if(br != null)
				br.close();
			client.close();
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

私聊系统

服务器端单收单发任务,因为对于每个监听到的客户端,当读到信息时,发给本用户和目标用户

public class ServerThread extends Thread{
	String UserId;
	Socket client = null;
	String talkTo;
	public ServerThread(Socket s) {
		this.client = s;
	}
	public void setUserId(String name) {
		this.UserId = name;
	}
	public void settalkTo(String target) {
		this.talkTo = target;
	}
	
	@Override
	public void run() {
		InputStream is = null;
		OutputStream os = null;
		try {
			is = client.getInputStream();
			os = client.getOutputStream();
			BufferedReader br = new BufferedReader(new InputStreamReader(is));
			PrintWriter pw = null;
			while(true) {
				String line = br.readLine();
				pw = new PrintWriter(os, true);
				pw.println(this.UserId +": "+ line);//发给本用户
				System.out.println("接收到有人发消息,开始发送给目标用户");
				Socket s = Server.map.get(talkTo);//发给目标用户
				pw = new PrintWriter(s.getOutputStream(),true);
				pw.println(this.UserId +": "+ line);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
public class Server {
	ServerSocket ss = null;
	static Map<String, Socket> map = new HashMap<>();
	public Server(ServerSocket ss) {
		this.ss = ss;
		System.out.println("服务器已启动");
	}
	public void work() {
		Socket client;
		try {
			while(true) {
				client = ss.accept();
				BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
				String newconnectId = br.readLine();
				String talkTo = br.readLine();
				System.out.println(newconnectId+"上线了-"+client.getInetAddress());
				map.put(newconnectId, client);
				ServerThread t = new ServerThread(client);
				t.setUserId(newconnectId);
				t.settalkTo(talkTo);
				t.start();
				System.out.println("当前连入服务器的用户有:"+map.size()+"个");
			}
		}catch (IOException e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) {
		Server s = null;
		try {
			s = new Server(new ServerSocket(8080));
			s.work();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

客户端,每个客户端先要向服务器发送用户ID和目标用户两条信息

public class Client {
	Socket s = null;
	String Id = null;
	String target = null;
	BufferedReader br = null;
	PrintWriter pw = null;
	public Client() {
		try {
			s = new Socket("localhost",8080);
			pw = new PrintWriter(s.getOutputStream(),true);
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public void send() {
		System.out.println("客户端已开启");
		System.out.println(s);
		Scanner sc = new Scanner(System.in);
		String line = null;
		System.out.println("请输入用户名:");
		Id = sc.nextLine();
		pw.println(Id);
		System.out.println("请输入聊天对象:");
		target = sc.nextLine();
		pw.println(target);
		while((line = sc.nextLine())!= null) {
			if(line.equalsIgnoreCase("exit"))
				break;
			pw.println(line);
		}
		sc.close(); 
	}
	public void receive() {
		Thread t = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					br = new BufferedReader(new InputStreamReader(s.getInputStream()));
					while(true) {
						System.out.println(br.readLine());
					}
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}
		});
		t.start();
	}
	public static void main(String[] args) {
		Client c = new Client();
		c.receive();
		c.send();
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值