一、網絡編程的基本概念
二、IP地址及端口號
三、通訊協議
四、TCP通訊代碼實踐
4.1消息傳遞
4.2文件上傳
五、UDP通訊代碼
5.1 UDP實現消息發送
5.2 使用UDP循環發送和接收消息
5.3使用UDP實現聊天
複製代碼
引例java
在學習網絡編程以前,咱們先看這樣一個例子。通常處於咱們這個年齡段的同窗,大都經歷過寫信/寄信的經歷,在寫信的時候,咱們通常都須要明確發送的地址、聯繫人信息以及所在地區的郵編,郵遞員能夠根據這上面的信息找到接收信件的人,接收人在閱讀過信件的內容後能夠以一樣的方式回信,這樣就使得身處異地的朋友間能夠進行通訊。編程
在這種狀況下,身處異地的朋友若是要進行通訊,必須同時知足如下條件服務器
1.接收人的所在地區
及詳細的住址
微信
2.須要有快遞員接收併發送
markdown
慢慢的,隨着科技的發展,咱們有手機通訊、QQ聊天和微信聊天等多種通訊方式,採用這些方式咱們能夠更便捷的進行通訊,可是不管是那種方式,通訊過程當中老是須要這樣兩個元素:地址
及傳輸過程
。網絡
在網絡通訊中,這兩個元素可使用更專業的名詞來代替:多線程
一、IP地址和端口號
併發
IP地址能夠幫助咱們找到通訊的接收方,端口號則能夠幫助咱們找到計算機中的具體應用,如QQ、微信等socket
二、傳輸協議
ide
協議是爲了幫助咱們更好的進行傳輸,如TCP、UDP、FTP、SMTP和HTTP協議等
IP地址
是互聯網協議地址,它是一種統一的地址格式,互聯網中的每一臺主機都會有一個邏輯地址。在計算機網絡中,localhost
(意爲「本地主機」,指「這臺計算機」)是給回的一個標準主機名,相對應的IP地址爲127.0.0.1
經過IP地址,咱們就能夠鏈接到指定的計算機了,可是若是要訪問目標計算機中的某個應用程序,還須要知道端口號。在計算中,不一樣的應用程序就是經過端口號來進行區分的。
在網絡編程中,可使用InetAddress
進行主機名解析
和反向解析
,即給定肯定的主機名,返回肯定的IP地址;給定IP地址,返回主機名
localhost
解析爲127.0.0.1
127.0.0.1
反向解析爲localhost
NOTE:不一樣協議的端口是能夠重複的,如UDP和TCP
端口的查詢操做
netstat -ano #查看全部的端口
netstat -ano | findstr "XXX" # 查看指定的端口
複製代碼
IP地址及端口號解決了通訊過程當中的地址問題,可是在計算機中,咱們還要解決如何通訊
問題,所謂通訊就是計算機間如何交流,而通訊協議
就是將計算機雙方遵循的一種規則和約定
(如同普通話、英語),它能夠經過通訊信道將處於不一樣地理位置的設備鏈接起來,可以實現信息的交換和資源共享。
在計算機網絡中,經常使用的協議就是TCP/IP
,它是協議簇,由多個子協議組成了,如咱們常見的TCP
、IP
、UDP
、ARP
等,咱們主要講解網絡編程中經常使用的TCP、UDP和IP
TCP協議是一種傳輸協議,面向鏈接、可靠的、基於字節流的傳輸層通訊協議
UDP是一種無鏈接的傳輸協議,無需創建鏈接就能夠發送數據包
IP協議整個TCP/IP協議族的核心,對上可載送傳輸層各類協議的信息,例如TCP、UDP等;對下可將IP信息包放到鏈路層,經過以太網等各類技術來傳送。
TCP和UDP對比
TCP
能夠類比於打電話,它具備如下特色
三次握手
),因此鏈接穩定可靠四次揮手
)UDP
能夠類比於發短信,它具備如下特色
TCP網絡編程須要如下Java類
InetAddress
:表示IP協議的地址,能夠解析IP地址和主機名
Socket
:實現客戶端的套接字,創建鏈接,套接字就是兩臺機器間通信的端點
ServerSocket
:實現服務端的套接字
客戶端
1.拿到服務端的地址及端口,InetAddress
2.創建Socket
鏈接
3.發送消息
public class TcpClient {
public static void main(String[] args) throws IOException {
Socket socket=null;
OutputStream os=null;
try {
//一、獲得服務端的地址,端口號
InetAddress inetAddress=InetAddress.getByName("127.0.0.1");
int port=9898;
//二、創建Socket鏈接
socket=new Socket(inetAddress,port);
//三、發送消息
os=socket.getOutputStream();
os.write("hello,Simon".getBytes());
}catch (Exception e){
e.printStackTrace();
}finally {
//關閉資源
os.close();
socket.close();
}
}
}
複製代碼
服務端
1.用ServerSocket
設置本身的端口號
2.等待鏈接,Socket
2.1:能夠等待一次鏈接,接收到消息後關閉
2.2:等待屢次鏈接,用while(true)把等待鏈接的方法包裹起來,重複接收消息
三、獲取發送端的消息
public class TcpServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket=null;
Socket socket=null;
InputStream is=null;
ByteArrayOutputStream baos=null;
try {
//一、設置本身的端口號
serverSocket=new ServerSocket(9898);
//二、等待客戶端的鏈接
socket=serverSocket.accept();
//三、讀取客戶端的消息
is=socket.getInputStream();
baos=new ByteArrayOutputStream();
byte[] buffer=new byte[1024];
int len;
while ((len=is.read(buffer))!=-1){
baos.write(buffer,0,len);
}
System.out.print(baos.toString());
}catch (Exception e){
e.printStackTrace();
}finally {
baos.close();
is.close();
socket.close();
serverSocket.close();
}
}
}
複製代碼
客戶端
1.建立一個鏈接
2.建立一個字節輸出流用於通訊
建立一個文件輸入流用於接收文件,而後將接收後的文件給字節輸出流用於通訊
3.輸出結束後,調用shutdownInput
方法通知服務端已經發送完畢
4.關閉鏈接資源
public class TcpFileClient {
public static void main(String[] args) throws Exception {
//一、建立鏈接
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9987);
//二、定義一個輸出流用於通訊
OutputStream os = socket.getOutputStream();
FileInputStream fis = new FileInputStream(new File("simon.png"));
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
//三、通知服務器,我已經結束了
socket.shutdownOutput();
//肯定服務器接收完畢,才能夠斷開鏈接,這一段代碼主要是接收服務端發出的結束信息
InputStream inputStream= socket.getInputStream();
byte[] buffer2=new byte[1024];
ByteArrayOutputStream baos=new ByteArrayOutputStream();
int len2;
while ((len2=inputStream.read(buffer2))!=-1){
baos.write(buffer2,0,len2);
}
System.out.print(baos.toString());
//關閉鏈接
baos.close();
inputStream.close();
os.close();
fis.close();
socket.close();
}
}
複製代碼
服務端
1.建立一個鏈接用於等待客戶端的接入
2.建立一個輸入流
建立一個文件輸出流,將輸入流輸出
3.關閉資源
public class TcpFileServerDemo02 {
public static void main(String[] args) throws Exception {
//設置端口號
ServerSocket serverSocket=new ServerSocket(9987);
//等待鏈接
Socket socket=serverSocket.accept();
//獲取輸入流
InputStream is=socket.getInputStream();
//文件輸出
FileOutputStream fos=new FileOutputStream(new File("snow.png"));
byte[] buffer=new byte[1024];
int len;
while ((len=is.read(buffer))!=-1){
fos.write(buffer,0,len);
}
//通知客戶端接收完畢
OutputStream os=socket.getOutputStream();
os.write("我已經接收結束,你能夠斷開了".getBytes());
//關閉資源
fos.close();
is.close();
socket.close();
serverSocket.close();
}
}
複製代碼
UDP
不須要鏈接,可是須要知道對方的地址和端口號,主要用到了如下Java類
DatagramPacket
:表示數據包,用於實現無鏈接分組傳送服務
DatagramSocket
:表示用於發送和接收數據報的套接字
UDP是不存在客戶端和服務端的概念,可是爲了編程模擬方便,咱們假定存在客戶端和服務端
客戶端
public class UdpClientDemo1 {
public static void main(String[] args) throws Exception {
//一、創建鏈接(這個端口號是客戶端的)
DatagramSocket datagramSocket=new DatagramSocket(9888);
//二、創建數據包
String msg="hello,Simon";
InetAddress inetAddress=InetAddress.getByName("127.0.0.1");
//數據,數據的起始位置,要發送的地址與端口號
DatagramPacket datagramPacket=new DatagramPacket(msg.getBytes(),0,msg.getBytes().length,inetAddress,9887);
//三、發送數據包
datagramSocket.send(datagramPacket);
//四、關閉數據流
datagramSocket.close();
}
}
複製代碼
服務端
public class UdpServerDemo2 {
public static void main(String[] args) throws Exception{
//創建鏈接,開放的端口
DatagramSocket datagramSocket=new DatagramSocket(9887);
//接收數據包
byte[] buffer=new byte[1024];
DatagramPacket datagramPacket=new DatagramPacket(buffer,0,buffer.length);
datagramSocket.receive(datagramPacket);
//打印數據包信息
System.out.println(datagramPacket.getAddress());
System.out.println(datagramPacket.getPort());
System.out.println(new String(datagramPacket.getData()));
//關閉鏈接
datagramSocket.close();
}
複製代碼
使用while(true)方法將客戶端中的發送數據和接收數據包裹起來,只要當他們知足必定的條件時(如輸入的字符串爲"bye"),退出便可。這樣就能夠達到循環發送和接收消息。
客戶端
public class UdpSend {
public static void main(String[] args) throws Exception {
//一、創建鏈接
DatagramSocket datagramSocket=new DatagramSocket(9888);
//二、建立數據包,從鍵盤輸入
InetAddress inetAddress=InetAddress.getByName("127.0.0.1");
int port=9887;
BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(System.in));
while (true){
String s=bufferedReader.readLine();
DatagramPacket datagramPacket=new DatagramPacket(s.getBytes(),0,s.getBytes().length,inetAddress,9887);
//三、發送數據
datagramSocket.send(datagramPacket);
if(s.equals("bye")){
break;
}
}
//四、關閉數據
datagramSocket.close();
}
}
複製代碼
服務端
public class UdpReceive{
public static void main(String[] args) throws Exception {
//一、創建鏈接
DatagramSocket datagramSocket=new DatagramSocket(9887);
while (true){
//二、接收數據包
byte[] buffer=new byte[1024];
DatagramPacket datagramPacket=new DatagramPacket(buffer,0,buffer.length);
datagramSocket.receive(datagramPacket);
//三、斷開鏈接
byte[] data=datagramPacket.getData();
String receiveData=new String(data,0,data.length);
System.out.println(receiveData);
if(receiveData.equals("bye")){
break;
}
}
datagramSocket.close();
}
}
複製代碼
使用UDP實現聊天,則客戶端和服務端(其實不存在客戶端和服務端的概念)既須要接收信息也須要發送消息,這就須要多線程的支持了。
咱們首先構造兩個接收類和發送的線程類,而後構造倆個用戶類進行通訊。
發送類
public class TalkSend implements Runnable {
DatagramSocket datagramSocket = null;
BufferedReader bufferedReader = null;
private int fromPort;
private int toPort;
private String toIp;
public TalkSend(int fromPort,int toPort,String toIp){
this.fromPort=fromPort;
this.toPort=toPort;
this.toIp=toIp;
//一、創建鏈接
try {
datagramSocket = new DatagramSocket(fromPort);
//二、建立數據包,從鍵盤輸入
bufferedReader = new BufferedReader(new InputStreamReader(System.in));
} catch (SocketException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
try {
String s = bufferedReader.readLine();
DatagramPacket datagramPacket = new DatagramPacket(s.getBytes(), 0, s.getBytes().length, new InetSocketAddress(toIp,toPort));
//三、發送數據
datagramSocket.send(datagramPacket);
if (s.equals("bye")) {
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
//四、關閉數據
datagramSocket.close();
}
}
複製代碼
接收類
public class TalkReceive implements Runnable{
DatagramSocket datagramSocket=null;
private int port;
private String msgFrom;
public TalkReceive(int port,String msgFrom) {
this.port=port;
this.msgFrom=msgFrom;
//一、創建鏈接
try {
datagramSocket=new DatagramSocket(port);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true){
try {
//二、接收數據包
byte[] buffer=new byte[1024];
DatagramPacket datagramPacket=new DatagramPacket(buffer,0,buffer.length);
datagramSocket.receive(datagramPacket);
//三、斷開鏈接
byte[] data=datagramPacket.getData();
String receiveData=new String(data,0,data.length);
System.out.println(msgFrom+": "+receiveData);
if(receiveData.equals("bye")){
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
datagramSocket.close();
}
}
複製代碼
用戶類1
public class TalkStudent {
public static void main(String[] args) {
new Thread(new TalkSend(7777,9999,"localhost")).start();
new Thread(new TalkReceive(8888,"小包")).start();
}
}
複製代碼
用戶類2
public class TalkTeacher {
public static void main(String[] args){
new Thread(new TalkSend(5555,8888,"localhost")).start();
new Thread(new TalkReceive(9999,"小郎")).start();
}
}
複製代碼