TCP/IP 实现通信图示:
服务器端: ServerSocket
第一步: 服务器端启动一个ServerSocket, 绑定到特定的端口号上,并对该端口号进行监听,等待用户的请求
ServerSocket serverSocket = new ServerSocket(5566);
第二步: 调用accept() 方法,监听端口号, 如果没有用户发送请求连接,这个accept方法是会一致阻塞的,用户发送了请求之后就会被这个方法监听到,并返回一个发送方的socket,(类型是Socket)
socket = serverSocket.accept()
第三步: 建立了连接之后,就通过I/O的方式进行通信,做数据的传递
InputStream In = socket.getInputStream(); //节点流
OutputStream out = socket.getOutputStream(); // 节点流
需要注意的一点是,服务器一直在监听,当它捕获到一个请求时,会为此请求启动一个线程,为此分配一个绑定到不同端口地址的套接字,而之前的那个端口号继续进行监听
客户端: Socket(套接字): 连接运行在网络上的两个程序间的双向通信的端点
第一步: 向服务器端发送连接请求(需要知道服务器的IP和端口号)
Socket socket = new Socket(InetAddress.getLocalHost(), 5566);
第二步: 上一步建好连接之后,就通过I/O的方式进行通信
InputStream In = socket.getInputStream(); //节点流
OutputStream out = socket.getOutputStream(); // 节点流
一个程序是只能占用一个端口号的,下面是一个小例子,通过线程的方式实现服务器端和客户端的通信,将读写操作分别建立线程
服务器端代码
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
//
public class ServerSocketTest {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(5566);
Socket socket;
while (true){
socket = serverSocket.accept(); // 如果没有客户端的请求,这一句是会一直阻塞的
// 启动读线程
serverReadThread readThread = new serverReadThread(socket);
readThread.start();
// 启动写线程
User user = new User("liuxw",18,"server");
serverWriteThread writeThread = new serverWriteThread(socket,user);
writeThread.start();
}
}
}
class serverReadThread extends Thread {
private Socket socket;
private User user;
public serverReadThread(Socket socket) {
this.socket = socket;
}
public void run() {
try {
// try 语句不要写在循环里面,要尽量写在循环外面,因为异常对象的创建与处理消耗时间
while (true) {
ObjectInputStream in = new ObjectInputStream( socket
.getInputStream());
ArrayList<User> list = (ArrayList<User>) in.readObject();
System.out.println(list.get(0).getName());
System.out.println(list.get(1).getName());
// 如何关闭呢, 如果直接加上in.close() 这样的话就会频繁的进行创建销毁,代价较大
// 解决方法,要么连接一直开着,要么定义一个特定字符来关闭流,要么使用连接池来管理连接
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class serverWriteThread extends Thread {
private Socket socket;
private User user;
public serverWriteThread(Socket socket, User user) {
this.socket = socket;
this.user = user;
}
public void run() {
try {
// try 语句不要写在循环里面,要尽量写在循环外面,因为异常对象的创建与处理消耗时间
while (true) {
ObjectOutputStream objOut = new ObjectOutputStream(socket.getOutputStream());
// ObjectOutputStream 也是可以把一个集合写进去的,读的时候按照集合的方式来读就行了
ArrayList<User> list = new ArrayList<User>();
User user2 = new User("cao",22,"sss");
list.add(user);
list.add(user2);
objOut.writeObject(list);
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
客户端程序:
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
public class ClientSocketTest {
public static void main(String[] args) throws Exception {
Socket socket = new Socket(InetAddress.getLocalHost(), 5566);
ObjectOutputStream objOut = new ObjectOutputStream(socket.getOutputStream());
User user = new User("wangsw", 20, "client");
// 启动读线程, 其实这里的操作和服务器端的操作是一致的,因此我们就不再重复的写另外一个类
serverReadThread readThread = new serverReadThread(socket);
readThread.start();
// 启动写线程
serverWriteThread writeThread = new serverWriteThread(socket,user);
writeThread.start();
}
}
UDP 实现网络通信:
UDP 是面向的无连接的通信方式,因此UDP基本上是不区分服务器端和客户端的,但是我们在这里可以进行区分一下,我们把首先启动进行监听UDP报文消息的叫做服务器端,把先进行发送数据的叫做客户端。
为什么我会想着在这里做一下区分呢?这是因为我在写下面的测试程序的时候,发现两个用户如果启动的顺序不对,那么是收不到报文的,当时我就想为什么顺序不对会接收不到呢?
这是因为,监听的一方先启动,那么就会一直对网络进行监听,当有人向他发送消息时他能捕获到,但是当发送的一方先启动,那么报文传输到网络中,一定时间内找不到要发送的对象,那么就会发生报文丢失的现象。
当时为了弄懂这个原因,我还看了帮助文档,却意外的发现了有一个connect方法,不是面向无连接的吗?为什么有一个connect连接方法,这个我会在下一篇中详细进行讲解。
现在先让我们来看看如何在java中实现UDP通信吧
java中使用DatagramSocket 和DatagramPacket 来实现UDP通信,Socket类是用来创建一个socket的,Packet是用来创建一个要发送的报文的。
socket.send(), socket.recieve()用来发送和接收数据包
需要注意的是,我们在发送数据包的时候,数据包里面要设置被发送方的IP和Port, 但是隐藏的我们自身的IP 和Port也会添加到这个数据包里面, 这样对方接收到我们发送的数据包,就能够从数据包里面获取到我们的IP和Port 然后就可以向我们发送数据包。
流程如下:
服务器端(先进行监听的一方)
第一步: 首选创建一个DatagramSocket,为其分配一个端口号,然后就会一直监听从这个端口号传进来的UDP数据包
DatagramSocket socket = new DatagramSocket(7000);
第二步: 创建一个DatagramPacket 包用来存储从该端口接收到的数据包,然后调用socket.receive() 方法,将收到的数据包放在创建的对象里。
byte[] buff = new byte[100];
DatagramPacket packetRecieve = new DatagramPacket(buff, 0, buff.length);
socket.receive(packetRecieve);
第三步: 获取数据包中的信息,并且根据数据包中发送方的IP和Port向客户端发送数据包。
String str = "world";
DatagramPacket packetSend = new DatagramPacket(str.getBytes(),
str.length(), packetRecieve.getAddress(),
packetRecieve.getPort());
socket.send(packetSend);
第四步:关闭socket
socket.close();
客户端的代码和这个差不多,只不过顺序颠倒一下,先发送后接收。
下面给出了一个实现了UDP通信的小例子
服务器端(先监听的)
// UDP传输是没有IO的,每次发送的信息的包里都包含了要发送对象的IP 和端口号 ,而TCP、IP 一旦建立连接就通过流来读取接收数据
public class UDPServerTest {
// 服务器端先接收消息,然后在发送消息
public static void main(String[] args) throws Exception {
// 自己开一个7000端口号,监听网络中的UDP数据包
DatagramSocket socket = new DatagramSocket(7000);
// 接收一个用户的packet
// 首先定义一个packet,这个packet 用来存储接收到的packet
byte[] buff = new byte[100];
DatagramPacket packetRecieve = new DatagramPacket(buff, 0, buff.length);
socket.receive(packetRecieve);
System.out.println(new String(buff, 0, packetRecieve.getLength())); // 输出接收到的消息
// 然后再向用户发送消息
// 首先将要发送的数据包封装到一个packet里面, 通过接收到的包获取对方的Ip 和端口号
String str = "world";
DatagramPacket packetSend = new DatagramPacket(str.getBytes(),
str.length(), packetRecieve.getAddress(),
packetRecieve.getPort());
socket.send(packetSend);
socket.close();
}
}
客户端(先发送的)
public class UDPClientTest {
// 客户端先发送消息
public static void main(String[] args) throws Exception {
// 先new 一个 socket ,其中设置你自己的端口号
DatagramSocket socket = new DatagramSocket(7001);
// socket.connect(InetAddress.getLocalHost(), 7000);
// socket.disconnect();
// 将你要发送的消息,封装到一个data
String str = "hello";
// 注意的是packet包中都是字节数组,所以数据要转化为字节数组
// 首先将消息封装到一个packet中,packet中包含了指定的对方的ip,port,还有发送给对方的消息(隐藏的:自己的ip,port也会在这个packet中)
DatagramPacket packetSend = new DatagramPacket(str.getBytes(), str.length(), InetAddress.getLocalHost(), 7000);
// 发送
socket.send(packetSend);
byte[] buff = new byte[100];
DatagramPacket packetRecieve = new DatagramPacket(buff, 0, buff.length);
socket.receive(packetRecieve);
System.out.println(new String(buff, 0, packetRecieve.getLength())); // 输出接收到的消息
socket.close();
}
}