目录
一、网络编程概述
计算机网络:是指将地理位置不同的具有独立功能的多态计算机及其外部设备,通过通信线路连接起来,
在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。
网络编程:就是用来实现网络互连的不同计算机上运行的程序间可以进行数据交换。
二、网络编程三要素
网络编程三要素分别为IP、端口号和。
2.1 IP概述
IP是每个设备在网络中的唯一标识,
每台网络终端在网络中都有一个独立的地址,我们在网络中传输数据就是使用这个地址,
ipconfig
在cmd命令行中输入ipconfig可以查看本机的IP地址,
ping
在cmd命令行中输入ping www.xxx.com可以测试是否连接到了某个网址,
其中本地回路地址为:127.0.0.1,广播地址为:255.255.255.255,
IP又分为IPV4和IPV6两个版本,
- IPV4:由4个字节组成,4个0-255。数量约为42亿,2011年初已经用尽了所有的IPV4地址。
- IPV6:一共由8组数据组成,每组为4个16进制数。
2.2端口号
端口号是每个程序在设备上的唯一标识,每个网络程序都要绑定一个端口号,
传输数据的时候除了确定发到哪台机器上,还要明确发到哪个程序,端口号范围是0-65535,
编写网络应用就需要绑定一个端口号,尽量使用1024以上的端口号,因为1024以下的基本被系统程序占用,
以下为一些常用端口:
- mysql:3306
- oracle:1521
- web:80
- tomcat:8080
- QQ:4000
2.3协议
协议是为了计算机网络中进行数据交换而建立的规则、标准或约定的集合。
在网络传输层一般有UDP和TCP两种协议,
- UDP:面向无连接,数据不安全,速度快。不区分客户端与服务端
- TCP:面向连接(三次握手),数据安全,速度降低。分为客户端和服务端
- 三次握手:客户端先向服务端发起请求,服务端响应请求,然后开始传输数据
三、Socket通信
Socket即套接字,
网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字,
通信的两端都有Socket,网络通信就是Socket之间的通信,数据在两个Socket之间通过IO流传输,
Socket在应用程序中创建,通过一种绑定机制与驱动程序建立关系,告诉自己所对应的IP和端口,
四、UDP传输
4.1 UDP简单数据传输接收
在java中实现UDP传输我们要用到DatagramSocket这个类,
DatagramSocket表示用于发送和接收数据包的Socket套接字,
而UDP传输分为三个阶段,
- 发送
- 创建DatagramSocket,随机端口号
- 创建DatagramPacket,指定数据,长度,地址,端口
- 使用DatagramSocket发送DatagramPacket
- 关闭DatagramSocket
- 接收
- 创建DatagramSocket,指定端口号
- 创建DatagramPacket,指定数据,长度,地址,端口
- 使用DatagramSocket接收DatagramPacket
- 关闭DatagramSocket
- 从DatagramPacket中获取数据
- 接收方获取ip和端口号
- String ip = packet.getAddress().getHostAddress()
- int port = packet.getPort()
我们首先新建两个类,分别实现send的receive数据,
import java.io.IOException;
import java.net.*;
public class send {
public static void main(String[] args) throws IOException {
String str="socket test";
DatagramSocket socket=new DatagramSocket();//创建socket
DatagramPacket packet= new DatagramPacket(str.getBytes(),str.getBytes().length,
InetAddress.getByName("127.0.0.1"),6666);//创建packet
socket.send(packet);//传输数据
socket.close();//关闭socket
}
}
import java.io.IOException;
import java.net.*;
public class receive {
public static void main(String[] args) throws IOException {
DatagramSocket socket=new DatagramSocket(6666);//创建指定端口号socket接收数据
DatagramPacket packet=new DatagramPacket(new byte[1024],1024);//指定数组和长度存储接受的数据
socket.receive(packet);
byte arr[]=packet.getData();//获取数据
int len=packet.getLength();//获取有效字节个数
System.out.println(new String(arr,0,len));
socket.close();
}
}
然后在命令行中对.class文件进行javac编译,获得.java文件,
接着先启动receive程序,然后启动send程序,就可以看到接收到了send发出的数据了,
4.2 UDP键盘录入传输的数据
我们试一下传输用户输入的数据,
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class send {
public static void main(String[] args) throws IOException {
Scanner sc=new Scanner(System.in);
DatagramSocket socket = new DatagramSocket();//创建socket
while(true) {
String str=sc.nextLine();
DatagramPacket packet = new DatagramPacket(str.getBytes(), str.getBytes().length,
InetAddress.getByName("127.0.0.1"), 6666);//创建packet
socket.send(packet);//传输数据
if("quit".equals(str)){
break;
}
}
socket.close();//关闭socket
}
}
import java.io.IOException;
import java.net.*;
public class receive {
public static void main(String[] args) throws IOException {
DatagramSocket socket=new DatagramSocket(6666);//创建指定端口号socket接收数据
DatagramPacket packet=new DatagramPacket(new byte[1024],1024);//指定数组和长度存储接受的数据
while(true) {
socket.receive(packet);
byte arr[] = packet.getData();//获取数据
int len = packet.getLength();//获取有效字节个数
String ip=packet.getAddress().getHostAddress();//获取IP地址
int port=packet.getPort();
if("quit".equals(new String(arr, 0, len))){
break;
}
System.out.println("IP:"+ip+" "+"Port:"+port+" "+"Data:"+new String(arr, 0, len));
}
socket.close();//关闭socket
}
}
还是javac编译两个java文件,然后先运行接收的程序,然后运行发送的,可以看到以下的通信内容,
4.3 UDP多线程传输
有人会觉得用两个程序很麻烦,我们可以用多线程在同一个程序内启动接收和发送线程,
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class socketTest {
public static void main(String[] args) {
new Receive().start();//开启接收线程
new Send().start();//开启发送线程
}
}
class Send extends Thread{
@Override
public void run() {
try {
Scanner sc = new Scanner(System.in);
DatagramSocket socket = new DatagramSocket();//创建socket
while (true) {
String str = sc.nextLine();
DatagramPacket packet = new DatagramPacket(str.getBytes(), str.getBytes().length,
InetAddress.getByName("127.0.0.1"), 6666);//创建packet
socket.send(packet);//传输数据
if ("quit".equals(str)) {
break;
}
}
socket.close();//关闭socket
}catch (IOException e){
e.printStackTrace();
}
}
}
class Receive extends Thread{
@Override
public void run() {
try {
DatagramSocket socket = new DatagramSocket(6666);//创建指定端口号socket接收数据
DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);//指定数组和长度存储接受的数据
while (true) {
socket.receive(packet);
byte arr[] = packet.getData();//获取数据
int len = packet.getLength();//获取有效字节个数
String ip = packet.getAddress().getHostAddress();//获取IP地址
int port = packet.getPort();
if ("quit".equals(new String(arr, 0, len))) {
break;
}
System.out.println("IP:" + ip + " " + "Port:" + port + " " + "Data:" + new String(arr, 0, len));
}
socket.close();//关闭socket
}catch (IOException e){
e.printStackTrace();
}
}
}
可以看到程序也正常发送和接收数据了,
4.4 UDP聊天图形化界面
我们接着用java的GUI做一下聊天的图形化界面,
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.text.SimpleDateFormat;
import java.util.Date;
public class GUIChart extends Frame {
public TextField tf;
public Button button_send;
public Button button_clear;
public Button button_record;
public Button button_shake;
public TextArea viewText;
public TextArea sendText;
private DatagramSocket socket;
private BufferedWriter bw;
public GUIChart(){//创建聊天窗体
initialWindow();
southPanel();
centerPanel();
event();
}
public void event() {
this.addWindowListener(new WindowAdapter() {//关闭窗口
public void windowClosing(WindowEvent e) {
try {
socket.close();
bw.close();
} catch (IOException e1) {
e1.printStackTrace();
}
System.exit(0);
}
});
button_send.addActionListener(new ActionListener() {//发送消息
public void actionPerformed(ActionEvent e) {
sentMessage();
}
});
sendText.addKeyListener(new KeyAdapter() {//敲回车键也是发送
public void keyReleased(KeyEvent e) {
if(e.getKeyCode()==KeyEvent.VK_ENTER){//e.isControlDown()判断ctrl键是否按下
sentMessage();
}
}
});
button_record.addActionListener(new ActionListener(){//记录功能
public void actionPerformed(ActionEvent e){
try {
logFile();
} catch (IOException e1) {
e1.printStackTrace();
}
}
});
button_clear.addActionListener(new ActionListener() {//清屏功能
public void actionPerformed(ActionEvent e) {
viewText.setText("");//将显示区内容设置为空
}
});
button_shake.addActionListener(new ActionListener() {//震动功能
public void actionPerformed(ActionEvent e) {
sentMessage(new byte[]{-1},tf.getText());//给对方发送字节-1,该字节表示要震动
}
});
}
public void shake() {
int x=this.getLocation().x;//获取窗口左上角横坐标位置
int y=this.getLocation().y;//获取窗口左上角纵坐标位置
//改变窗口位置
for (int i = 0; i < 5; i++) {
try {
this.setLocation(x+10,y+10);
Thread.sleep(20);
this.setLocation(x+10,y-10);
Thread.sleep(20);
this.setLocation(x-10,y-10);
Thread.sleep(20);
this.setLocation(x-10,y+10);
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.setLocation(x,y);
}
public void logFile() throws IOException {
bw.flush();//将缓冲区的内容刷新到记录中
FileInputStream fis=new FileInputStream("config.txt");//读取本地记录文件
ByteArrayOutputStream baos=new ByteArrayOutputStream();//在内存中创建缓冲区
int len;
byte arr[]=new byte[8192];
while((len=fis.read(arr))!=-1){
baos.write(arr,0,len);//将记录读取到内存缓冲区中
}
String str=baos.toString();//将内存缓冲区中的内容转换成字符串
viewText.setText(str);//将聊天记录显示在聊天窗口
fis.close();
}
public void sentMessage(byte arr[],String ip){
try {
DatagramPacket packet=new DatagramPacket(arr,arr.length,InetAddress.getByName(ip),9999);
socket.send(packet);//发送消息
}catch (IOException ie){
ie.printStackTrace();
}
}
public void sentMessage() {
String message=sendText.getText();//获取发送区域的内容
String ip=tf.getText();//获取要发送的ip地址
ip=ip.trim().length()==0?"255.255.255.255":ip;//若ip地址为空,则赋值为255.255.255.255,即向所有人发送消息
sentMessage(message.getBytes(),ip);//发送消息
String time=getCurrentTime();//获取当前时间
String context=time+" 我向"+ip+"发送了:\r\n"+message+"\r\n";//要发送的信息
viewText.append(context);//将信息添加到显示窗口
try {
bw.write(context);//将信息写到本地数据库中
} catch (IOException e) {
e.printStackTrace();
}
sendText.setText("");//将发送区域清空
}
public String getCurrentTime() {
Date d=new Date();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
return sdf.format(d);
}
public void centerPanel() {//创建窗体中心的面板
Panel center=new Panel();
//显示文本区域
viewText = new TextArea();
viewText.setEditable(false);//设置显示区域不可写
viewText.setBackground(Color.WHITE);//设置背景为白色,若不设置不可写区域默认为灰色背景
viewText.setFont(new Font("myFont",Font.PLAIN,15));
//发送文本区域
sendText = new TextArea(6,1);
sendText.setFont(new Font("myFont",Font.PLAIN,15));
center.setLayout(new BorderLayout());//设置为边界布局
center.add(sendText,BorderLayout.SOUTH);
center.add(viewText,BorderLayout.CENTER);
this.add(center,BorderLayout.CENTER);
}
public void southPanel() {//创建窗体下部的面板
Panel south=new Panel();
//创建文本字段存储IP地址
tf = new TextField(15);
tf.setText("127.0.0.1");
button_send = new Button("send");
button_clear = new Button("clear");
button_record = new Button("record");
button_shake = new Button("shake");
south.add(tf);
south.add(button_send);
south.add(button_clear);
south.add(button_record);
south.add(button_shake);
this.add(south,BorderLayout.SOUTH);//将panel放在下部
}
public void initialWindow() {
this.setLocation(500,200);//设置位置
this.setSize(400,600);//设置大小
new Receive().start();//开启接收线程
try {
socket = new DatagramSocket();//创建通信码头
bw=new BufferedWriter(new FileWriter("config.txt",true));//创建缓存流即外部数据库,存储聊天记录(true会保留上一次的聊天记录)
} catch (Exception e) {
e.printStackTrace();
}
this.setVisible(true);
}
class Receive extends Thread{
public void run(){//使用多线程,让接收和发送同时执行
try {
DatagramSocket socket=new DatagramSocket(9999);
DatagramPacket packet=new DatagramPacket(new byte[8192],8192);
while(true) {
socket.receive(packet);//接收信息
byte arr[] = packet.getData();
int len = packet.getLength();
if(arr[0]==-1&&len==1){//如果接收到的为震动指令
shake();
continue;
}
String message = new String(arr, 0, len);//转换为字符串
String time = getCurrentTime();//获取当前时间
String ip = packet.getAddress().getHostAddress();//获取ip
String context=time + " " + ip + "向我发送了:\r\n" + message + "\r\n";//输出的信息
viewText.append(context);
bw.write(context);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new GUIChart();
}
}
下面是图形化界面截图,
然后我们生成jar文件方便直接运行,依次选择右上角的Project Structure->Project Settings->Artifacts->Add->JAR->From modules with dependencies...,
然后创建JAR文件,选择Main Class为我们的GUIChart.class,下面是创建的路径,默认即可,选择ok,
接着选择菜单中的Build->Build Artifacts->GUIChart->Build,然后就会在刚刚的路径下生成JAR文件,
我们就可以直接运行了。
五、TCP传输
5.1TCP协议
TCP的连接建立过程又称为TCP三次握手,
- 首先发送方主机向接收方主机发起一个建立连接的同步(SYN)请求;
- 接收方主机在收到这个请求后向发送方主机回复一个同步/确认(SYN/ACK)应答;
- 发送方主机收到此包后再向接收方主机发送一个确认(ACK)。
完成三次握手后才开始传输数据。
在传输数据的时候,TCP分为客户端和服务端,
- 客户端
- 创建Socket连接服务器(指定ip地址,端口号),通过ip地址找对应的服务器
- 调用Socket的getInputStream()和getOutputStream()方法获得和服务端相连的IO流
- 输入流可以读取服务端输出流写出的数据
- 输出流可以写出数据到服务端的输入流
- 服务端
- 创建ServerSocket(需要指定端口号)
- 调用ServerSocket的accept()方法接受一个客户端请求,得到一个Socket
- 调用Socket的getInputStream()和getOutputStream()方法获取和客户端相连的IO流
- 输入流可以读取客户端输出流写出的数据
- 输出流可以写出数据到客户端的输入流
5.2TCP通信练习
我们首先创建服务器类TCPServer.class和客户端类TCPClient.class,
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServer {
public static void main(String[] args) throws IOException {
ServerSocket server=new ServerSocket(10203);//创建服务器ServerSocket,并指定端口号
while(true) {
Socket socket = server.accept();//接收客户端的请求,得到一个socket
new Thread(){//创建为多线程的服务器
public void run(){
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));//获取与客户端相连的输入流,并将字节流包装成字符流
PrintStream ps = new PrintStream(socket.getOutputStream());//获取与客户端相连的输出流,也可以用BufferedWriter,但是要注意每写一行数据要调用newline方法进行换行
ps.println("Java is No.1");//服务器向客户端写出数据
System.out.println(br.readLine());//接收客户端发出的字符串并打印
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
}
}
import java.io.*;
import java.net.Socket;
public class TCPClient {
public static void main(String[] args) throws IOException {
Socket socket=new Socket("127.0.0.1",10203);//创建socket连接服务端,指定了ip地址和端口号
BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));//获取与服务端相连的输入流,将字节流包装成字符流
PrintStream ps=new PrintStream(socket.getOutputStream());//获取与服务端相连的输出流,也可以用BufferedWriter,但是要注意每写一行数据要调用newline方法进行换行
System.out.println(br.readLine());//读取服务器发过来的数据并打印
ps.println("I agree.");//客户端向服务器写出数据
socket.close();
}
}
然后编译运行,结果如下:
六、网络编程练习
6.1客户端与服务端之间的通信
要求客户端向服务器键盘录入写字符串,服务器(多线程)将字符串反转后写回,客户端再次读取到是反转后的字符串。
我们还是创建两个类,一个是服务器Test1Server,一个是客户端Test1Client,
package practice;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Test1Server {
public static void main(String[] args) throws IOException {
ServerSocket server=new ServerSocket(10203);//创建服务器,指定端口号
System.out.println("服务器已启动,绑定10203端口");
while(true){
Socket socket=server.accept();//接收客户端的请求
new Thread(){//开启一条线程
public void run() {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));//获取输入流
PrintStream ps = new PrintStream(socket.getOutputStream());//获取输出流
String line=br.readLine();//读取客户端写过来的数据
line=new StringBuilder(line).reverse().toString();//利用StringBuilder的反转方法将字符串反转
ps.println(line);//向客户端写入反转后的字符串
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
}
}
package practice;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class Test1Client {
public static void main(String[] args) throws IOException {
Scanner sc=new Scanner(System.in);//创建键盘录入对象
Socket socket=new Socket("127.0.0.1",10203);//创建客户端指定IP地址和端口号
BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));//获取输入流
PrintStream ps=new PrintStream(socket.getOutputStream());//获取输出流
ps.println(sc.nextLine());//向服务器写读取的字符串
System.out.println(br.readLine());//读取反转后的结果并打印
socket.close();
}
}
然后先启动服务器,在启动客户端,可以看到输出结果
6.2客户端向服务器上传文件
我们还是创建服务器类和客户端类,
package practice;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Test2Server {
public static void main(String[] args) throws IOException {
ServerSocket server=new ServerSocket(10203);//创建服务器,指定端口号
System.out.println("服务器已启动,绑定10203端口");
while(true){
Socket socket=server.accept();//接收客户端的请求
new Thread(){//开启一条线程
public void run() {
try {
InputStream is=socket.getInputStream();//获取字节输入流
BufferedReader br = new BufferedReader(new InputStreamReader(is));//将字节输入流包装为字符流
PrintStream ps = new PrintStream(socket.getOutputStream());//获取输出流
String fileName=br.readLine();//读取要上传的文件名
File dir=new File("update");
dir.mkdir();//创建文件夹存储文件
File file=new File(dir,fileName);//封装成file对象
if(file.exists()){
ps.println("存在");
socket.close();
return;
}else{
ps.println("不存在");
}
FileOutputStream fos=new FileOutputStream(file);//从网络读取数据,存储到服务器本地
byte arr[]=new byte[8192];
int len;
while((len=is.read(arr))!=-1){
fos.write(arr,0,len);//将数据写到服务器本地
}
fos.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
}
}
package practice;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class Test2Client {
public static void main(String[] args) throws IOException {
File file=getFile();//获取到要上传的文件
Socket socket=new Socket("127.0.0.1",10203);//创建客户端指定IP地址和端口号
BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));//获取输入流
PrintStream ps=new PrintStream(socket.getOutputStream());//获取输出流
ps.println(file.getName());//将文件名发送到服务器
String result=br.readLine();//读取文件是否存在服务器的结果
if("存在".equals(result)){
System.out.println("您上传的文件在服务器上已存在,请勿重复上传");
socket.close();
return;
}
FileInputStream fis=new FileInputStream(file);//如果不存在则上传文件
byte arr[]=new byte[8192];
int len;
while((len=fis.read(arr))!=-1){
ps.write(arr,0,len);//将数据写到服务器上
}
fis.close();
socket.close();
}
private static File getFile() {
Scanner sc=new Scanner(System.in);//创建键盘录入对象
System.out.println("请输入一个文件路径:");
while(true){
String line=sc.nextLine();
File file=new File(line);
if(!file.exists()){
System.out.println("您输入的文件路径不存在,请重新录入:");
}else if(file.isDirectory()){
System.out.println("您输入的是文件夹路径,请输入一个文件路径:");
}else{
return file;
}
}
}
}
然后先启动服务器,再启动客户端,输入要上传的文件,发现上传成功了,