写在前面:
Java为网络编程提供了丰富的库,我们能通过调用Socket套接字的方法实现服务器与客户端的双通信。
注意点:
需要注意的是端口的对应,端口可以理解为窗户,服务器只能通过某个端口(窗户)与外界进行数据通信,客户端也如此。
所以
客户端与服务器端的通信就可以理解为服务器端的一个端口<------>客户端的一个端口 (”<--->“代表是全双工通信,收发信息互不影响)
为什么要用多线程?
因为要想客户端和服务器端互相发数据且互不干扰(以在控制台发数据为例),必须让读取键盘输入流为另一个线程,否则就会一直在等待。
TCP实现客户端与服务器端的通信:
效果:
?
服务器类:
package com4_18;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
//服务器接受客户端信息
class STR implements Runnable {
private BufferedReader br;
public static boolean sw=false;
public STR(BufferedReader bufferedReader) {
this.br = bufferedReader;
}
String mString = null;
@Override
public void run() {
try {
if (!sw) {
while (!(mString = br.readLine()).equals("bye")) {
System.out.println("接受来自客户端的消息:" + mString);
}
System.out.println("客户端关闭");
br.close();
}
} catch(Exception e){
System.out.println("异常");
}
}
}
//服务器端给客户端发送数据
class STT implements Runnable{
private PrintWriter pWriter=null;
Scanner in=new Scanner(System.in);
public STT(PrintWriter p){
this.pWriter=p;
}
@Override
public void run() {
while (!STR.sw) {
String str = in.nextLine();
pWriter.println(str);
System.out.println("向客户端发送数据:" + str);
if(str.equals("bye"))STR.sw=true;
pWriter.flush();
}
pWriter.close();
try {
ServerTest.server.close();
ServerTest.client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class ServerTest {
private int port=7890;
public static ServerSocket server;
public static Socket client;
public void connect() {
// 1.构造方法:创建一个服务,并占用一个端口号
try {
Scanner scanner =new Scanner(System.in);
server = new ServerSocket (port);
client= server.accept();
if (client!=null) {
System.out.println("成功连接到客户端");
}
InputStream is = client.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
OutputStream oStream = client.getOutputStream();
PrintWriter pWriter = new PrintWriter(oStream);
pWriter.println("你好!客户端,我是服务器端!");
pWriter.flush();
STR str=new STR(br);
Thread thread=new Thread(str);
thread.start();
STT stt=new STT(pWriter);
Thread thread2=new Thread(stt);
thread2.start();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}//端口号一般从1025开始
// 2.调用accept方法,让服务处于等待状态,并获得Socket
// Socket client= server.accept();
// 3.接收客户端发送的数据
// InputStream is = client.getInputStream(); //BufferReader
// 4.向服务器发送数据
// OutputStream os = client.getOutputStream(); //PrintWriter
// 5.server.close(); //网络通信结束后关闭服务
}
public static void main(String[] args) {
// TODO 自动生成的方法存根
new ServerTest().connect();
}
}
客户端类:
package com4_18;
import jdk.nashorn.internal.ir.CatchNode;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
import javax.sound.sampled.Port;
/**
* 客户端从服务器接收数据并打印到控制台上的线程
*/
class CTR implements Runnable {
private BufferedReader br;
public static boolean sw=false;
public CTR(BufferedReader bufferedReader) {
this.br = bufferedReader;
}
String mString = null;
@Override
public void run() {
try {
while (!(mString = br.readLine()).equals("bye")) {
System.out.println("客户端接受来自服务器的消息:" + mString);
}
br.close();
System.out.println("服务器端关闭");
} catch (Exception e) {
System.out.println("异常");
}
}
}
/**
* 客户端从控制台发送给服务器线程
*/
class CTT implements Runnable{
private PrintWriter pWriter=null;
Scanner in=new Scanner(System.in);
public CTT(PrintWriter p){
this.pWriter=p;
}
@Override
public void run() {
while (!CTR.sw) {
String str = in.nextLine();
pWriter.println(str);
System.out.println("客户端向服务器发送数据:" + str);
if(str.equals("bye")) CTR.sw=true;
pWriter.flush();
}
pWriter.close();
try {
ClientTest.client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class ClientTest {
private int port=7890;
public static Socket client;
public void connect() {
Scanner scanner =new Scanner(System.in);
// 1.与服务器建立连接,如通过IP地址(字符串)和端口构造:
try {
InetAddress address = InetAddress.getLocalHost();
client = new Socket(address,port);
if (client!=null) {
System.out.println("成功联系到服务器!");
}
//2.接收服务器发送的数据
InputStream is = client.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
OutputStream oStream = client.getOutputStream();
PrintWriter pWriter = new PrintWriter(oStream);
pWriter.println("你好!服务器,我是客户端!");
pWriter.flush();
CTT ctt=new CTT(pWriter);
Thread thread=new Thread(ctt);
thread.start();
CTR ctr=new CTR(br);
Thread thread1=new Thread(ctr);
thread1.start();
} catch (UnknownHostException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
public static void main(String[] args) {
// TODO 自动生成的方法存根
new ClientTest().connect();
}
}
UDP客户端与服务器端的通信(和上面的TCP一样,也是通过多线程的方式实现)
与上面的TCP不同的是,我设定服务器端默认给客户端回执信息和接受信息,而上面的TCP则是能通过控制台输入发送给客户端信息。
效果:
?
?
与客户端相关类:
客户端接受数据线程类:
package com4_25;
import java.io.IOException;
import java.net.DatagramPacket;
import static com4_25.ClientUDP.datagramSocket;
//客户端接受数据包
class ClientUDPR extends Thread{
//从服务器端接受信息
private DatagramPacket datagramPacket;
public ClientUDPR() {
System.out.println("this is UDPR");
}
public void run(){
while (true) {
byte[] buf = new byte[50];
datagramPacket = new DatagramPacket(buf, buf.length);
try {
//接受数据包
datagramSocket.receive( datagramPacket);
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
byte[] redata;
redata=datagramPacket.getData();
String strRecieved = new String(redata);
System.out.println("从服务器端:" + datagramPacket.getAddress() +
" 的端口:" + datagramPacket.getPort() +
"接受到的内容:" + strRecieved);
}
}
}
客户端发送数据线程类:
package com4_25;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;
public class ClientUDP extends Thread {
private int port;
public static int remotePort;
InetAddress serverAdress;
public static DatagramSocket datagramSocket = null;
private DatagramPacket datagramPacket = null; //包
Scanner in = new Scanner(System.in);
public ClientUDP(int port, InetAddress serverAdress, int remotePort) {
super();
this.port = port;
this.serverAdress = serverAdress;
this.remotePort = remotePort;
try {
this.datagramSocket = new DatagramSocket(port);
} catch (SocketException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
public void run() {
while (true) {
String sentStr = in.nextLine();//从控制台接受数据
byte[] buf;
buf = sentStr.getBytes();
datagramPacket = new DatagramPacket(buf, buf.length,
serverAdress, remotePort);//发送数据包
try {
datagramSocket.send(datagramPacket);
System.out.println("已发送给服务器");
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
}
启动客户端测试类:
package com4_25;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class UDPTest {
public static void main(String[] args) {
// TODO 自动生成的方法存根
InetAddress address;
try {
address = InetAddress.getLocalHost();
new ClientUDP(8080, address, 8001).start();
new ClientUDPR().start();
} catch (UnknownHostException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
服务器类:
package com4_25;
import com.sun.org.apache.bcel.internal.generic.NEW;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
public class SeverUDP extends Thread {
private int port;
private DatagramSocket datagramSocket= null;
private DatagramPacket datagramPacket= null;
public SeverUDP(int port) {
super();
this.port = port;
try {
datagramSocket = new DatagramSocket(port);
} catch (SocketException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
public void run() {
if (datagramSocket==null) {
System.out.println("没有成功创建服务器!");
}
while (true) {
try {
/**
* 服务器从客户端接受消息
*/
byte[]buf =new byte[50];
datagramPacket = new DatagramPacket(buf,buf.length );
datagramSocket.receive(datagramPacket);
InetAddress address = datagramPacket.getAddress();
int remotePort = datagramPacket.getPort();
buf = datagramPacket.getData();
String recievedData = new String(buf);
System.out.println("从地址:"+address+"端口:(客户端)"+
remotePort+"内容:" );
char []mystr=recievedData.toCharArray();
for (int i = 0; i <mystr.length; i++) {
if(mystr[i]==' ')break;
System.out.print(mystr[i]);
}
String sendStr = "服务器的回执(表示服务器已经收到)";
/**
* 回敬给客户端消息
*/
buf = sendStr.getBytes();
System.out.println(new String(buf));
datagramPacket = new DatagramPacket(buf, buf.length, address, remotePort);
datagramSocket.send(datagramPacket);
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// TODO 自动生成的方法存根
new SeverUDP(8001).start();
}
}
值得注意的是:TCP服务器与客户端的通信需要建立连接,而UDP服务器与客户端的通信则是以接受数据包SocketPackage的形式获取报文,而报文的长度一开始再创建Socketpacket时又无从得知。
|