网络通讯基本三要素:
1、IP地址
2、端口号
3、传输协议(UDP、TCP等)
UDP
将数据及源和目的封装在数据包中,不需要建立连接。每个数据包的大小限制在64K内。因无连接,是不可靠协议。速度快。
应用:聊天工具、在线视频等
TCP
建立连接,形成传输数据的通道。
在连接中进行大数据量传输,通过三次握手完成连接,是可靠协议。效率稍低。
应用:FTP(文件传输协议)
TCP/IP模型:
应用层、传输层、网际层、主机至网络层
Socket编程
Socket是为网络服务提供的一种机制。通信的两端都有Socket。网络通信其实就是Socket之间的通信,数据在两个Socket间通过IO传输。
1、UDP建立Socket
DatagramSocket 发送和接收数据报包的套接字
DatagramPacket 数据报包
发送流程:
(1) 建立UDP的Socket服务。
(2) 将要发送的数据封装到数据包中。
(3) 通过UDP的Socket服务将数据包发送出去。
(4) 关闭Socket服务。
//1.
DatagramSocket ds = new DatagramSocket(8888);
//2.
String str = "UDP传输演示,哥们来了!";
byte[] buf = str.getBytes();
DatagramPacket dp = new DatagramPacket(buf, buf.length, InetAddress.getByName(192.168.1.100), 10000);
//3.
ds.send(dp);
//4.
ds.close();
接收流程:
(1) 建立UDP的Socket服务,因为是要接收数据,必须明确一个端口号。
(2) 创建数据包,用于存储接收到的数据,方便用数据包对象的方法解析这些数据。
(3) 使用Socket服务的receive方法将接收的数据存储到数据包中。
(4) 通过数据包的方法解析数据包中的数据。
(5) 关闭资源。
//1.
DatagramSocket ds = new DatagramSocket(10000);
//2.
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf, buf.length);
//3.
ds.receive(dp);//阻塞式
//4.
String ip = dp.getAddress().getHostAddress();
int port = dp.getPort();
String text = new String(dp.getData(), 0, dp.getLength());
System.out.println(ip + ":" + port + ":" + text);
//5.
ds.close();
聊天代码(群聊)
需求:编写一个聊天程序。有收数据和发数据的部分,需要同时执行,故要用到多线程。
因为收和发的动作是不一致的,所以要定义两个run方法,而且两个方法要封装到不同的类中。
—Send代码—
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class Send implements Runnable {
private DatagramSocket ds;
public Send(DatagramSocket ds) {
this.ds = ds;
}
@Override
public void run() {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while((line = br.readLine()) != null) {
if("886".equals(line))
break;
byte[] buf = line.getBytes();
DatagramPacket dp = new DatagramPacket(buf, line.length(), InetAddress.getByName("196.128.1.255"), 10000);
ds.send(dp);
}
} catch (IOException e) {
throw new RuntimeException("发送端失败");
}
}
}
—Receive代码—
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Receive implements Runnable {
private DatagramSocket ds;
public Receive(DatagramSocket ds) {
this.ds = ds;
}
@Override
public void run() {
try {
while(true) {
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf, buf.length);
ds.receive(dp);
String ip = dp.getAddress().getHostAddress();
String text = new String(buf, 0, dp.getLength());
System.out.println(ip + ":" + text);
if(text.equals("886")) {
System.out.println(ip + "退出聊天室");
}
}
} catch (IOException e) {
throw new RuntimeException("接受失败");
}
}
}
—主代码—
public class SocketChat {
public static void main(String[] args) {
DatagramSocket sendSocket;
DatagramSocket receiveSocket;
try {
sendSocket = new DatagramSocket();
receiveSocket = new DatagramSocket(10000);
new Thread(new Send(sendSocket)).start();
new Thread(new Receive(receiveSocket)).start();
} catch (SocketException e) {
e.printStackTrace();
}
}
}
2、TCP建立Socket
TCP分客户端和服务端。客户端对应的对象是Socket,服务端对应的对象是ServerSocket。
客户端:
在该对象建立时,就可以去连接指定主机。因为TCP是面向连接的,所以在建立socket服务时,就要有服务端存在并连接成功。形成通路后,在该通道进行数据的传输。
步骤:
1、创建TCP客户端Socket服务,并制定要连接的主机和端口。
2、通过getOutputStream()来获取输出字节流。
3、使用输出流,将数据写出。
4、关闭资源。
public class TcpClient {
public static void main(String[] args) {
try {
//1.
Socket s = new Socket("116.228.249.84", 10002);
//2.
OutputStream out = s.getOutputStream();
//3.
out.write("TCP 哥们来了!".getBytes());
//4.
s.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端:
1、创建服务端Socket服务,通过ServerSocket对象。
2、服务端必须对外提供一个端口,否则客户端无法连接。
3、获取连接过来的客户端对象。通过ServerSocket的accept()完成。
4、通过客户端对象获取Socket流读取客户端发来的信息,并打印在控制台上。
5、关闭资源,关客户端、服务端。
public class TcpServer {
public static void main(String[] args) {
try {
//1.创建服务器端对象,提供一个端口
ServerSocket ss = new ServerSocket(10002);
//2.获取连接过来的客户端对象
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
//3.通过socket对象获取输入流,要读取客户端发来的数据
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
String text = new String(buf, 0, len);
System.out.println(ip + ":" + text);
s.close();
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端与服务端的交互:
客户端代码
public class TcpClient2 {
public static void main(String[] args) {
try {
Socket s = new Socket("116.228.249.84", 10003);
OutputStream os = s.getOutputStream();
os.write("服务端,你好!".getBytes());
//读取服务端返回的数据
InputStream is = s.getInputStream();
byte[] buf = new byte[1024];
int len = is.read(buf);
String text = new String(buf, 0, len);
System.out.println(text);
s.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端代码
public class TcpServer2 {
public static void main(String[] args) {
try {
ServerSocket ss = new ServerSocket(10003);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
InputStream is = s.getInputStream();
byte[] buf = new byte[1024];
int len = is.read(buf);
String text = new String(buf, 0, len);
System.out.println(ip + ":" + text);
OutputStream os = s.getOutputStream();
os.write("哥们收到!".getBytes());
s.close();
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
文本转换TCP客户端和服务端
客户端键盘输入文字发送至服务端,服务端打印出客户端的文字,并返回大写文字显示在客户端控制台。
Client端代码
public class TcpClient3 {
public static void main(String[] args) {
try {
Socket s = new Socket("116.228.249.84", 10004);
//获取键盘录入
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
//Socket输出流
//BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
//socket输入流,读取服务端返回的大写数据
BufferedReader br1 = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = null;
while((line = br.readLine()) != null) {
if("over".equals(line))
break;
pw.println(line);
String upperStr = br1.readLine();
System.out.println(upperStr);
}
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Server端代码
public class TcpServer3 {
public static void main(String[] args) {
try {
ServerSocket ss = new ServerSocket(10004);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip + "......Connected");
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
String line = null;
while((line = br.readLine()) != null) {
System.out.println(line);
pw.println(line.toUpperCase());
}
s.close();
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
1、上面练习中之所以客户端结束后, 服务端也随之结束的原因在于: 客户端的socket关闭后, 服务端获取的客户端socket读取流也关闭了, 因此读取不到数据, line = bufIn.readLine()为null, 循环结束,ServerSocket的close方法也就执行关闭了。
2、 上面练习中的客户端和服务端的PrintWriter对象out获取到数据后, 一定要刷新, 否则对方( 服务端或客户端) 就获取不到数据, 程序便无法正常执行。 刷新操作可以通过PrintWriter类的println()方法实现, 也可以通过PrintWriter类的flush()方法实现。 但是, 由于获取数据的方法是BufferedReader对象bufIn的readLine()方法( 阻塞式方法) , 此方法只有遇到“\r\n”标记时, 才认为数据读取完毕, 赋值给String对象line。 所以, 使用PrintWriter类的flush()方法刷新数据时一定要记得追加“\r\n”!
上传文件至服务端(TCP复制文件)
客户端代码
public class TextClient {
public static void main(String[] args) {
try {
Socket s = new Socket("116.228.249.84", 10005);
BufferedReader br = new BufferedReader(new FileReader("d:\\xxx.txt"));
PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
String line = null;
while((line = br.readLine()) != null) {
pw.println(line);
}
//告诉服务端,客户端写完了
s.shutdownOutput();
BufferedReader br1 = new BufferedReader(new InputStreamReader(s.getInputStream()));
String str = br1.readLine();
System.out.println(str);
br.close();
s.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端代码
public class TextServer {
public static void main(String[] args) {
try {
ServerSocket ss = new ServerSocket(10005);
Socket s = ss.accept();
System.out.println(s.getInetAddress().getHostAddress() + "...connected");
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
BufferedWriter bw = new BufferedWriter(new FileWriter("d:\\server.txt"));
String line = null;
while((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
}
PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
pw.println("上传成功");
bw.close();
s.close();
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
向服务器上传图片(单用户版本)
客户端:
public class PicClient {
public static void main(String[] args) {
try {
Socket s = new Socket("192.168.1.100", 10007);
FileInputStream fis = new FileInputStream("e:\\1.jpg");
OutputStream os = s.getOutputStream();
byte[] buf = new byte[1024];
int len = 0;
while((len = fis.read(buf)) != -1) {
os.write(buf, 0, len);
}
s.shutdownOutput(); //通知服务器数据已经写完
InputStream is = s.getInputStream();
byte[] buf1 = new byte[1024];
int num = is.read(buf1);
System.out.println(new String(buf1, 0, num));
fis.close();
s.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端
局限性:无法并发访问。当A连接上后,B要连接只有等待。因为服务端还没有处理完A的请求,无法循环回来处理B的accept方法。
public class PicServer {
public static void main(String[] args) {
try {
ServerSocket ss = new ServerSocket(10007);
Socket s = ss.accept();
InputStream is = s.getInputStream();
FileOutputStream fos = new FileOutputStream("f:\\server.jpg");
byte[] buf =new byte[1024];
int len = 0;
while((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
}
OutputStream os = s.getOutputStream();
os.write("上传成功".getBytes());
s.close();
ss.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端并发上传图片
服务端:
将每个客户端封装到一个单独的线程,这样就可以处理多个客户端请求。
只要明确了每一个客户端要在服务端执行的代码即可,将该代码存入run()方法。
public class PicServer {
public static void main(String[] args) {
try {
ServerSocket ss = new ServerSocket(10007);
while(true) {
Socket s = ss.accept();
new Thread(new PicThread(s)).start();
}
//ss.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class PicThread implements Runnable {
private Socket s;
public PicThread(Socket s) {
this.s = s;
}
@Override
public void run() {
int count = 0;
String ip = s.getInetAddress().getHostAddress();
try {
System.out.println(ip + "...connected");
InputStream is = s.getInputStream();
File file = new File(ip + "(" + count + ")" + ".jpg");
while(file.exists()) {
file = new File(ip + "(" + (count++) + ")" + ".jpg");
}
FileOutputStream fos = new FileOutputStream("f:\\server.jpg");
byte[] buf =new byte[1024];
int len = 0;
while((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
}
OutputStream os = s.getOutputStream();
os.write("上传成功!".getBytes());
s.close();
} catch (Exception e) {
throw new RuntimeException(ip + "上传失败!");
}
}
}
客户端:
public class PicClient {
public static void main(String[] args) {
try {
if(args.length != 1) {
System.out.println("请选择一个jpg格式的图片");
return;
}
File file = new File(args[0]);
if(!(file.exists() && file.isFile())) {
System.out.println("该文件有问题,要么不存在,要么不是文件");
return;
}
if(file.getName().endsWith(".jpg")) {
System.out.println("图片格式错误,请重新选择");
return;
}
if(file.length() > 1024*1024*5) {
System.out.println("文件过大,没安好心");
return;
}
Socket s = new Socket("116.228.249.84", 10007);
FileInputStream fis = new FileInputStream(file);
OutputStream os = s.getOutputStream();
byte[] buf = new byte[1024];
int len = 0;
while((len = fis.read(buf)) != -1) {
os.write(buf, 0, len);
}
s.shutdownOutput(); //通知服务器数据已经写完
InputStream is = s.getInputStream();
byte[] buf1 = new byte[1024];
int num = is.read(buf1);
System.out.println(new String(buf1, 0, num));
fis.close();
s.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端并发登录
客户端:
public class LoginClient {
public static void main(String[] args) {
try {
Socket s = new Socket("192.168.1.254", 10008);
//读取输入的用户名
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
//向socket写入用户名
PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
//读取服务端返回的信息
BufferedReader br1 = new BufferedReader(new InputStreamReader(s.getInputStream()));
for(int i=0; i<3; i++) { //只能尝试登录3次
String line = br.readLine();
if(line == null) //如果停止访问,直接跳出循环
break;
String info = br1.readLine();
pw.println("info:" + info);
if(info.contains("欢迎")) //如果登陆成功,直接跳出循环
break;
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端:
public class LoginServer {
public static void main(String[] args) {
try {
ServerSocket ss = new ServerSocket(10008);
while(true) {
Socket s = ss.accept();
new Thread(new LoginThread(s)).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class LoginThread implements Runnable {
private Socket s;
public LoginThread(Socket s) {
this.s = s;
}
@Override
public void run() {
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip + "...connected");
try {
for (int i=0; i<3; i++) { //限制访问3次
//读取客户端发来的登录名
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
String name = br.readLine();
if(name == null) //如果客户端停止访问,结束循环
break;
//读取.txt文件中存储的用户名
BufferedReader br1 = new BufferedReader(new FileReader("user.txt"));
//向客户端写出信息
PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
String line = null;
boolean flag = false;
while((line = br1.readLine()) != null) {
if(line.equals(name)) { //用户名正确
flag = true;
break;
}
}
if(flag) { //如果用户登录正确,打印信息并跳出循环
System.out.println(name + ",已登录");
pw.println("欢迎光临!");
break;
}
}
s.close();
} catch (Exception e) {
throw new RuntimeException(ip + "校验失败!");
}
}
}