我们都知道本地计算机在线程通信时可以根据每个进程的PID来识别不同的程序
但如果一台计算机和另外一台计算机进行通信时光靠进程的PID是不行的,因为进程在生成时的PID也是随机的。
网络中的TCP/IP协议加上端口可以唯一确定一台计算机的某个进程,IP确定了是哪台计算机,端口号又确定了了是哪个进程。
本机进行数据交流时可以通过OutputStream、InputStream这样的IO数据流来进行信息传递
需要通过网络传递数据时就可以用Socket这个类了。
数据在网络里也是一层套一层地包装好然后发给对方的,如果把数据比作货车上的货品,Socket差不多就是货车负责记录自己从哪里来(自己的本地IP地址),要到哪个工厂(对方的IP地址),货品都对应工厂的哪个仓库(端口)。
根据上图可知,首先得有一个服务器来等待客户端发数据
实例化一个ServerSocket对象,并传入一个端口参数,这里建议用2000到65535的参数,因为之前的端口可能已经被系统某个进程使用了。这里服务器端口参数为8888,所以想和他连接的客户端程序端口也要设置成8888。new ServerSocket(8888);
既然是服务器,当然也要知道自己的IP,但是new ServerSocket(8888)并没有设置
虽然用的是一个参数的构造方法但也会调用三个参数的构造方法,同时将端口号传了过去
backlog是连接队列,系统以及提供了默认值,想修改的话也可以用两个参数的构造方法
在三个参数的构造方法里又自动实例化了一个InetSocketAddress对象
InetSocketAddress已经自动调用其他方法获取到了本地IP地址。
这就是唯一确定的服务器进程实例化的大致流程。
数据流都是一层套一层,而ServerSocket是最外层,所以accept()方法可以获取内部的某层数据流,应该可以这样理解。
接下来就是数据流之间的互相传递数据了,需要注意的是:
在用Socket通信时,由于是网络通信所以要用网络数据流,比如常见的DataInputStream,否则数据格式不对。
如果用键盘写入数据推荐用BuffereReader写,用Scanner直接写给网络数据流的readUTF()会出错。因为Scanner从键盘获取的值并不是IO数据流的格式,就没法在它的基础上继续包装。而BuffereReader符合数据流的格式从而可以在它的基础上在包装成网络数据流格式。
此例子仅是单工通信,也就是客户端写一个服务器读取一个,然后服务器再写一个客户端再读一个。
服务器代码如下:public class ServeTest {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
Socket accept = serverSocket.accept();
InputStream inputStream = accept.getInputStream();
OutputStream outputStream = accept.getOutputStream();
DataInputStream dis= new DataInputStream(inputStream);
DataOutputStream dos=new DataOutputStream(outputStream);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String account=dis.readUTF();
String password=dis.readUTF();
System.out.println("账号:"+account+",密码:"+password);
while (!account.equals("哔哔小子")||!password.equals("123")) {
System.out.println("输入错误");
dos.writeUTF("输入错误");
account=dis.readUTF();
password=dis.readUTF();
}
dos.writeUTF("输入正确");
System.out.println("验证通过");
while(true){
String info=dis.readUTF();
System.out.print("客户端说:"+info+"n");
if (info.equals("bye"))
break;
System.out.print("我:");
dos.writeUTF(br.readLine());
System.out.println();
}
br.close();
dos.close();
dis.close();
}
}
客户端的Socket初始化和ServerSocket基本上差不多,需要注意的是:
既然是在本机上的模拟网络通信,所以能用的IP地址就是本机的IP地址,实例化Socket时传入的IP地址可以是本机IP地址也可以是"127.0.0.1",这个特殊地址是回路地址,就是用来测试本地连接用的。
如果服务器或者客户端还在读取数据,对方对应的Scoket先结束了,会报错。
客户端只要不结束,服务器的网络数据流在read()时就会一直进入阻塞状态直到对方发来数据
客户端代码如下:public class ClientTest {
public static void main(String[] args) throws Exception, IOException {
Socket socket = new Socket("127.0.0.1",8888);
Scanner scanner = new Scanner(System.in);
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
DataOutputStream dos = new DataOutputStream(outputStream);
DataInputStream dis = new DataInputStream(inputStream);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("输入账号:");
//String account=scanner.nextLine();必须要有参数接收,否则直接data写会出错
dos.writeUTF(br.readLine());
System.out.println("输入密码");
//String password=scanner.nextLine();也是问题
dos.writeUTF(br.readLine());
String info=dis.readUTF();
while (info.equals("输入错误")){
System.out.println("输入有误,请重新输入!");
System.out.println("输入账号:");
dos.writeUTF(br.readLine());
System.out.println("输入密码");
dos.writeUTF(br.readLine());
info=dis.readUTF();
}
System.out.println("已连接到主机");
System.out.println("进入聊天模式");
while(true){
System.out.print("我:");
String message=br.readLine();
System.out.println();
dos.writeUTF(message);
if (message.equals("bye"))
break;
message=dis.readUTF();
System.out.print("服务器说:"+message+"n");
}
br.close();
dos.close();
dis.close();
}
}