目录
TCP通信
Socket原理
Socket简介
获取本地地址和端口号
获取远端地址和端口号
获取网络输入流和网络输出流
close方法
Socket通信模型
Server端ServerSocket监听
Client端Socket连接
C S端通信模型
Server端多线程模型
简易聊天室
服务端Server
客户端Client
socket通常称作"套接字”, 用于描述IP地址和端口,是一个通信链的句柄。
在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket ,并绑定到一个端口.上,不同的端口对应于不同的服务。
应用程序通常通过"套接字”向网络发出请求或者应答网络请求。Socket和ServerSocket类位于java.net包中。
ServerSocket用于服务端,Socket是 建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。
java.net.Socket为套接字类,其提供了很多方法,其中我们可以通过Socket获取本地的地址以及端口1号。
一int getLocalPort ( )
该方法用于获取本地使用的端口号
- InetAddress getLocalAddress( )
该方法用于获取套接字绑定的本地地址
使用InetAddress获取本地的地址方法:
- String getC anonicalHostName( )
获取此IP地址的完全限定域名。
- String getHostAddress( )
返回IP地址字符串(以文本表现形式)。
public void testSocket(throws Exception {
Socket socket = new Socket(" localhost" 8088);
InetAddress add = socket.getLocalAddress();
System.out.println(add.getCanonicalHostName();
System.out.println(add.getHostAddress();
System.out.println(socket.getLocalPort();
}
通过Socket获取远端的地址以及端口]号。
- int getPort( )
该方法用于获取远端使用的端口号
- InetAddress .getInetAddress( )
该方法用于获取套接字绑定的远端地址
public void testSocket(throws Exception {
Socket socket = new Socket(" localhost" 8088);
InetAddress inetAdd = socket.getInetAddress();
System. out.println(
inetAdd.getCanonicalHostName();
System. out. println(inetAdd.getHostAddress());
System. out.println(socket.getPort);
}
通过Socket获取输入流与输出流,这两个方法是使用
Socket通讯的关键方法。
- InputStream getInputStream( )
该方法用于返回此套接字的输入流。
- OutputStream .getOutputStream()
该方法用于返回此套接字的输出流。
public void testSocket(throws Exception {
Socket socket = new Socket("localhost" ,8088);
InputStream in = socket.getInputStream0;
OutputStream out = socket.getOutputStream0);
}
当使用Socket进行通讯完毕后, 要关闭Socket以释放系统资源。
- void close( )
关闭此套接字。当关闭了该套接字后也会同时关闭,由此获取的输入流与输出流。
java.net.ServerSocket是运行于服务端应用程序中。通常创建ServerSocket需要指定服务端口1号,之后监听Socket的连接:
//创建ServerSocket并申请服务端口8088
ServerSocket server = new ServerSocket(8088);
/*方法会产生阻塞,直到某个Socket连接,并返回请求连
接的Socket*/
Socket socket = server.accept();
当服务端创建ServerSocket并通过accept(方法侦听后,我们就可以通过在客户端应用程序中创建Socket来向服务端发起连接。
需要注意的是,创建Socket的同时就发起连接,若连接异常会抛出异常。
//参数1 :服务端的IP地址,参数2:服务端的服务端口
Socket socket = new Socket( "localhost" ,8088);
若想使一个服务端可以支持多客户端连接, 我们需要解决以下问题:循环调用accept方法侦听客户端的连接
使用线程来处理单一客户端的数据交互,因为需要处理多客户端,所以服务端要周期性循环调用accept方法,但该方法会产生阻塞,所以与某个客户端的交互就需要使用线程来并发处理。
package socket;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
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.Arrays;
import java.util.Collections;
import java.util.List;
/**
* 聊天室的服务端
* @author 陈
*
*/
public class Server {
/*
* 服务端使用的是ServerSocket
* 它有两个作用:
*1.:向系统申请端口.
*2.:接收请求该端口的所有客户端的连接
*/
private ServerSocket server;
/*
* 内部类可以访问外部类的属性,因此所有的ClinetHandler都可以访问到外部类
* Server属性,所以这里离定义一个数组保存所有ClinetHandler对应客户端的
* 输出流就可以做到共享了,以便于每一个客户端发消息过来对应的ClinetHandler
* 都可以通过遍历这个数组拿到所有对应客户端的输出流广播消息
*/
// private PrintWriter allout={};
private List allout=
Collections.synchronizedList(new ArrayList<>());
/**
* 服务端构造方法,用来初始化服务端
*/
public Server(){
try {
/*
* 实例化的同时想系统申请服务端口,客户端Socket 就是通过这个
* 了该端口,如果再启动一遍时还申请该端口就会提示被占用了.
* 2:如果没有启动过两次,那说明系统其它程序占用了该端口,需要
* 更换一个可用的
*/
System.out.println("正在启动服务端...");
server = new ServerSocket(8088);
System.out.println("服务端启动完毕!");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 服务端开始工作方法
*/
public void start(){
try {
/*
* ServerSocket 提供的方法
* Socket accept()
* 该方法是一个阻塞方法,调用该方法后程序"卡住",此时开始等待
* 客户端的连接,一旦客户端实例化Socket并连接服务端这边申请的
* 端口(8088)时,accept方法会立即返回一个Socket实例,此时等于
* 和客户端建立了连接.服务端通过返回的这个Socket就可以与客户端
* 进行交互的.
* 多次调用accept方法可以接受多个客户端的连接.
*/
while(true){
System.out.println("等待客户端连接...");
Socket socket = server.accept();
System.out.println("一个客户端连接了!");
//启动一个线程处理该客户端交互
ClinetHandler handler = new ClinetHandler(socket);
Thread t = new Thread(handler);
t.start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Server server = new Server();
server.start();
}
/**
* 该线程任务用来与指定客户端交互
* @author 陈
*
*/
private class ClinetHandler implements Runnable{
private Socket socket;
private String host;//用来记录客户端IP地址信息:
// private String message;// B方案
public ClinetHandler(Socket socket){
this.socket = socket;
//通过socket远端计算机地址信息(对于服务端这边而言远端就是客户端)
host = socket.getInetAddress().getHostAddress();
}
public void run() {
PrintWriter pw =null;
try{
/*
* Socket 提供的方法:
* InputStream getInputStream();
* 通过该方法获取输入流读取的字节是远端计算机发送过来的字节
*/
InputStream in = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(in,"UTF-8");
BufferedReader br = new BufferedReader(isr);
OutputStream out = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
BufferedWriter bw = new BufferedWriter(osw);
pw = new PrintWriter(bw,true);
/*
* 将输出流存入共享数组allout中
*
*/
// synchronized(allout){
// //1.allout数组扩容
// allout = Arrays.copyOf(allout, allout.size()+1);
//
// //2.将输出流存入数组最后一个位置
// allout[allout.length-1] = pw;
// }
allout.add(pw);
System.out.println(host+"上线了,当前在线人数:"+allout.size());
String message ="";
while((message = br.readLine())!=null){
String info = host+"说:"+message;
System.out.println(info);
// System.out.println(host+"说:"+message);// B方案
// synchronized(allout){
// //回复所有客户端
// for(int i=0;ip.println(info)
// (p)->p.println(host+"说"+message) B方案
);
}
}catch(Exception e){
}finally{
//处理客户端断开连接操作
// synchronized(allout){
// //将当前客户端的输出流从数组allout中删除
// for(int i=0;i
package socket;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
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.net.UnknownHostException;
import java.util.Scanner;
/**
* 聊天室的客户端
* @author 陈
*
*/
public class Client {
/*
* java.net.Socket 套接字
* Socket 封装了TCP协议的通讯细节,使用它就可以与服务端建立网络连接了,
* 并且进行通讯,这里的通讯是以两条流的读写完成与服务端的数据交换的
*/
private Socket socket;
/**
* 客户端构造方法,用与初始化客户端
*/
public Client(){
try {
/*
* 实例化Socket 时需要传入两个参数:
*1:服务端的地址信息:(IP)
*2.:服务端打开的接口
*
* 我们可以通过IP找到网络上的服务端计算机,通过其打开的端口
* 可以连接到服务端应用程序.
* 127.0.0.1 //localhost
*/
System.out.println("正在连接服务端...");
socket = new Socket("localhost",8088);
System.out.println("已连接服务端!");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 客户端开始工作的方法
*/
public void start(){
try {
//先启动用于读取服务端发过来消息的线程
ServerHandler handler = new ServerHandler();
Thread t = new Thread(handler);
t.start();
/*
* Socket提供的方法:
* OutputStream getOutputStream()
* 通过socket的该方法获取输出流写出的字节会通过网络发送给
* 远端计算机.
*/
OutputStream out = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
BufferedWriter bw = new BufferedWriter(osw);
PrintWriter pw = new PrintWriter(bw,true);
Scanner scanner = new Scanner(System.in);
while(true){
String message = scanner.nextLine();
pw.println(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args){
Client client = new Client();
client.start();
}
/**
* 该线程负责循环读取服务端发送过来的消息
* @author 陈
*
*/
private class ServerHandler implements Runnable{
public void run() {
try{
InputStream in = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(in,"UTF-8");
BufferedReader br = new BufferedReader(isr);
String message ="";
while((message= br.readLine())!=null){
//读取服务端发送回来的一行字符串
System.out.println(message);
}
}catch(Exception e){
}
}
}
}