基本套接字TCP和UDP
TCP的Java支持
? ? ?協(xié)議相當(dāng)于相互通信的程序間達(dá)成的一種約定,它規(guī)定了分組報(bào)文的結(jié)構(gòu)、交換方式、包含的意義以及怎樣對(duì)報(bào)文所包含的信息進(jìn)行解析,TCP/IP協(xié)議族有IP協(xié)議、TCP協(xié)議和UDP協(xié)議。現(xiàn)在TCP/IP協(xié)議族中的主要socket類型為流套接字(使用TCP協(xié)議)和數(shù)據(jù)報(bào)套接字(使用UDP協(xié)議)。
? ? TCP協(xié)議提供面向連接的服務(wù),通過它建立的是可靠地連接。Java為TCP協(xié)議提供了兩個(gè)類:Socket類和ServerSocket類。一個(gè)Socket實(shí)例代表了TCP連接的一個(gè)客戶端,而一個(gè)ServerSocket實(shí)例代表了TCP連接的一個(gè)服務(wù)器端,一般在TCP Socket編程中,客戶端有多個(gè),而服務(wù)器端只有一個(gè),客戶端TCP向服務(wù)器端TCP發(fā)送連接請(qǐng)求,服務(wù)器端的ServerSocket實(shí)例則監(jiān)聽來(lái)自客戶端的TCP連接請(qǐng)求,并為每個(gè)請(qǐng)求創(chuàng)建新的Socket實(shí)例,由于服務(wù)端在調(diào)用accept()等待客戶端的連接請(qǐng)求時(shí)會(huì)阻塞,直到收到客戶端發(fā)送的連接請(qǐng)求才會(huì)繼續(xù)往下執(zhí)行代碼,因此要為每個(gè)Socket連接開啟一個(gè)線程。服務(wù)器端要同時(shí)處理ServerSocket實(shí)例和Socket實(shí)例,而客戶端只需要使用Socket實(shí)例。另外,每個(gè)Socket實(shí)例會(huì)關(guān)聯(lián)一個(gè)InputStream和OutputStream對(duì)象,我們通過將字節(jié)寫入套接字的OutputStream來(lái)發(fā)送數(shù)據(jù),并通過從InputStream來(lái)接收數(shù)據(jù)。
?
TCP連接的建立步驟
客戶端向服務(wù)器端發(fā)送連接請(qǐng)求后,就被動(dòng)地等待服務(wù)器的響應(yīng)。典型的TCP客戶端要經(jīng)過下面三步操作:
? ?1、創(chuàng)建一個(gè)Socket實(shí)例:構(gòu)造函數(shù)向指定的遠(yuǎn)程主機(jī)和端口建立一個(gè)TCP連接;
? ?2.通過套接字的I/O流與服務(wù)端通信;
? ?3、使用Socket類的close方法關(guān)閉連接。
?
?服務(wù)端的工作是建立一個(gè)通信終端,并被動(dòng)地等待客戶端的連接。典型的TCP服務(wù)端執(zhí)行如下兩步操作:
? ? ?1、創(chuàng)建一個(gè)ServerSocket實(shí)例并指定本地端口,用來(lái)監(jiān)聽客戶端在該端口發(fā)送的TCP連接請(qǐng)求;
? ? ?2、重復(fù)執(zhí)行:
? ? ? ? ? ?1)調(diào)用ServerSocket的accept()方法以獲取客戶端連接,并通過其返回值創(chuàng)建一個(gè)Socket實(shí)例;
? ? ? ? ? ?2)為返回的Socket實(shí)例開啟新的線程,并使用返回的Socket實(shí)例的I/O流與客戶端通信;
? ? ? ? ? ?3)通信完成后,使用Socket類的close()方法關(guān)閉該客戶端的套接字連接。
DEMO
?
? 客戶端
?import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
public class Exercise {
public void tcpExample(){
try {
Socket socket = new Socket("192.168.138.46",8344);
System.out.println("Connection ....to server sending echo string");
OutputStream out = socket.getOutputStream();
System.out.println("發(fā)送數(shù)據(jù)……");
out.write("測(cè)試連接并發(fā)送數(shù)據(jù)到服務(wù)器成功……".getBytes());
socket.close();
System.out.println("客戶端已經(jīng)關(guān)閉");
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
Exercise e = new Exercise();
//e.getInterfaceExample();
e.tcpExample();
}
}
服務(wù)端
package NET;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
public class TCPServer {
//設(shè)置緩沖區(qū)的大小
private static final int BUFSIZE = 50;
public static void tcpsever(){
try {
ServerSocket serSocket = new ServerSocket(8344);
int recvMsgSize = 0;
int total=0;
byte[] receive = new byte[BUFSIZE];
while(true){
//此方法返回一個(gè)服務(wù)器的關(guān)聯(lián)socket,用此進(jìn)行數(shù)據(jù)交換
Socket clntSock = serSocket.accept();
SocketAddress socadd = clntSock.getRemoteSocketAddress();
System.out.println("clint address:"+socadd);
InputStream in = clntSock.getInputStream();
while((total=in.read(receive, recvMsgSize, BUFSIZE-recvMsgSize))!=-1){
recvMsgSize += total;
}
System.out.println("從客戶端收到的數(shù)據(jù):"+new String(receive));
clntSock.close();
System.out.println("關(guān)閉和此客戶端的連接……");
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String args[]){
tcpsever();
}
}
?
UDP的Java支持
? ? UDP協(xié)議提供的服務(wù)不同于TCP協(xié)議的端到端服務(wù),它是面向非連接的,屬不可靠協(xié)議,UDP套接字在使用前不需要進(jìn)行連接。實(shí)際上,UDP協(xié)議只實(shí)現(xiàn)了兩個(gè)功能:
? ? 1)在IP協(xié)議的基礎(chǔ)上添加了端口;
? ? 2)對(duì)傳輸過程中可能產(chǎn)生的數(shù)據(jù)錯(cuò)誤進(jìn)行了檢測(cè),并拋棄已經(jīng)損壞的數(shù)據(jù)。
?
? ? Java通過DatagramPacket類和DatagramSocket類來(lái)使用UDP套接字,客戶端和服務(wù)器端都通過DatagramSocket的send()方法和receive()方法來(lái)發(fā)送和接收數(shù)據(jù),用DatagramPacket來(lái)包裝需要發(fā)送或者接收到的數(shù)據(jù)。發(fā)送信息時(shí),Java創(chuàng)建一個(gè)包含待發(fā)送信息的DatagramPacket實(shí)例,并將其作為參數(shù)傳遞給DatagramSocket實(shí)例的send()方法;接收信息時(shí),Java程序首先創(chuàng)建一個(gè)DatagramPacket實(shí)例,該實(shí)例預(yù)先分配了一些空間,并將接收到的信息存放在該空間中,然后把該實(shí)例作為參數(shù)傳遞給DatagramSocket實(shí)例的receive()方法。在創(chuàng)建DatagramPacket實(shí)例時(shí),要注意:如果該實(shí)例用來(lái)包裝待接收的數(shù)據(jù),則不指定數(shù)據(jù)來(lái)源的遠(yuǎn)程主機(jī)和端口,只需指定一個(gè)緩存數(shù)據(jù)的byte數(shù)組即可(在調(diào)用receive()方法接收到數(shù)據(jù)后,源地址和端口等信息會(huì)自動(dòng)包含在DatagramPacket實(shí)例中),而如果該實(shí)例用來(lái)包裝待發(fā)送的數(shù)據(jù),則要指定要發(fā)送到的目的主機(jī)和端口。
?
UDP的通信建立的步驟
UDP客戶端首先向被動(dòng)等待聯(lián)系的服務(wù)器發(fā)送一個(gè)數(shù)據(jù)報(bào)文。一個(gè)典型的UDP客戶端要經(jīng)過下面三步操作:
? ? 1、創(chuàng)建一個(gè)DatagramSocket實(shí)例,可以有選擇地對(duì)本地地址和端口號(hào)進(jìn)行設(shè)置,如果設(shè)置了端口號(hào),則客戶端會(huì)在該端口號(hào)上監(jiān)聽從服務(wù)器端發(fā)送來(lái)的數(shù)據(jù);
? ? 2、使用DatagramSocket實(shí)例的send()和receive()方法來(lái)發(fā)送和接收DatagramPacket實(shí)例,進(jìn)行通信;
? ? 3、通信完成后,調(diào)用DatagramSocket實(shí)例的close()方法來(lái)關(guān)閉該套接字。
?
由于UDP是無(wú)連接的,因此UDP服務(wù)端不需要等待客戶端的請(qǐng)求以建立連接。另外,UDP服務(wù)器為所有通信使用同一套接字,這點(diǎn)與TCP服務(wù)器不同,TCP服務(wù)器則為每個(gè)成功返回的accept()方法創(chuàng)建一個(gè)新的套接字。一個(gè)典型的UDP服務(wù)端要經(jīng)過下面三步操作:
1、創(chuàng)建一個(gè)DatagramSocket實(shí)例,指定本地端口號(hào),并可以有選擇地指定本地地址,此時(shí),服務(wù)器已經(jīng)準(zhǔn)備好從任何客戶端接收數(shù)據(jù)報(bào)文;
? ? 2、使用DatagramSocket實(shí)例的receive()方法接收一個(gè)DatagramPacket實(shí)例,當(dāng)receive()方法返回時(shí),數(shù)據(jù)報(bào)文就包含了客戶端的地址,這樣就知道了回復(fù)信息應(yīng)該發(fā)送到什么地方;
? ? 3、使用DatagramSocket實(shí)例的send()方法向服務(wù)器端返回DatagramPacket實(shí)例。
UDP Socket Demo
這里有一點(diǎn)需要注意:
UDP程序在receive()方法處阻塞,直到收到一個(gè)數(shù)據(jù)報(bào)文或等待超時(shí)。由于UDP協(xié)議是不可靠協(xié)議,如果數(shù)據(jù)報(bào)在傳輸過程中發(fā)生丟失,那么程序?qū)?huì)一直阻塞在receive()方法處,這樣客戶端將永遠(yuǎn)都接收不到服務(wù)器端發(fā)送回來(lái)的數(shù)據(jù),但是又沒有任何提示。為了避免這個(gè)問題,我們?cè)诳蛻舳耸褂肈atagramSocket類的setSoTimeout()方法來(lái)制定receive()方法的最長(zhǎng)阻塞時(shí)間,并指定重發(fā)數(shù)據(jù)報(bào)的次數(shù),如果每次阻塞都超時(shí),并且重發(fā)次數(shù)達(dá)到了設(shè)置的上限,則關(guān)閉客戶端。
DEMO‘
?
?package NET;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import com.sun.jmx.snmp.InetAddressAcl;
public class UDP {
private final static int TIMEOUT = 3000;
private final static int BUFSIZE = 50;
private final static int tries = 5;
/**
* udp連接的客戶端
* @throws SocketException
* @throws UnknownHostException
*/
public void udpclnt() throws SocketException, UnknownHostException{
int tril = 0;
DatagramSocket socket = new DatagramSocket();
String art = "UDP連接測(cè)試成功……";
byte[] by = art.getBytes();
InetAddress address = null;
address = InetAddress.getByName("lenovo-PC");
socket.setSoTimeout(0);
DatagramPacket send = new DatagramPacket(by,by.length,address,30000 );
try {
socket.send(send);
} catch (IOException e) {
tril++;
System.out.println("進(jìn)行重發(fā):剩余重發(fā)次數(shù)"+ (tries-tril));
}
socket.close();
}
/**
* udp連接的服務(wù)器
*/
public void udpServer(){
try {
DatagramSocket socket = new DatagramSocket(30000);
socket.setSoTimeout(TIMEOUT);
DatagramPacket receive = new DatagramPacket(new byte[50], BUFSIZE);
socket.receive(receive);
System.out.println("連接的客戶端IP:"+receive.getAddress().getHostAddress()+"端口:"+receive.getPort());
byte[]re= receive.getData();
System.out.println("收到的數(shù)據(jù):"+new String(re));
} catch (SocketException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String []args){
final UDP udp = new UDP();
Thread thread = new Thread(){
public void run(){
try {
udp.udpclnt();
} catch (SocketException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
thread.start();
udp.udpServer();
}
}
?
? 總結(jié):
1、編寫TCP客戶端程序,在實(shí)例化Socket類時(shí),要注意,底層的TCP協(xié)議只能處理IP協(xié)議,如果傳遞的第一個(gè)參數(shù)是主機(jī)名字而不是你IP地址,Socket類具體實(shí)現(xiàn)的時(shí)候會(huì)將其解析成相應(yīng)的地址,若因?yàn)槟承┰蜻B接失敗,構(gòu)造函數(shù)會(huì)拋出一個(gè)IOException異常。
2、TCP協(xié)議讀寫數(shù)據(jù)時(shí),read()方法在沒有可讀數(shù)據(jù)時(shí)會(huì)阻塞等待,直到有新的數(shù)據(jù)可讀。另外,TCP協(xié)議并不能確定在read()和write()方法中所發(fā)送信息的界限,接收或發(fā)送的數(shù)據(jù)可能被TCP協(xié)議分割成了多個(gè)部分。說(shuō)到這里,其實(shí)是與內(nèi)部結(jié)構(gòu)相關(guān)的
TCP在發(fā)送之后會(huì)把所有的數(shù)據(jù)復(fù)制到緩沖區(qū)中,而UDP則send之后所有的消息都在發(fā)送途中。
3、編寫TCP服務(wù)器端的程序?qū)⒃赼ccept()方法處阻塞,以等待客戶端的連接請(qǐng)求,一旦取得連接,便要為每個(gè)客戶端的連接建立一個(gè)Socket實(shí)例來(lái)進(jìn)行數(shù)據(jù)通信。
4、在UDP程序中,創(chuàng)建DatagramPacket實(shí)例時(shí),如果沒有指定遠(yuǎn)程主機(jī)地址和端口,則該實(shí)例用來(lái)接收數(shù)據(jù)(盡管可以調(diào)用setXXX()等方法指定),如果指定了遠(yuǎn)程主機(jī)地址和端口,則該實(shí)例用來(lái)發(fā)送數(shù)據(jù)。
5、UDP程序在receive()方法處阻塞,直到收到一個(gè)數(shù)據(jù)報(bào)文或等待超時(shí)。由于UDP協(xié)議是不可靠協(xié)議,如果數(shù)據(jù)報(bào)在傳輸過程中發(fā)生丟失,那么程序?qū)?huì)一直阻塞在receive()方法處,這對(duì)客戶端來(lái)說(shuō)是肯定不行的,為了避免這個(gè)問題,我們?cè)诳蛻舳耸褂肈atagramSocket類的setSoTimeout()方法來(lái)制定receive()方法的最長(zhǎng)阻塞時(shí)間,并指定重發(fā)數(shù)據(jù)報(bào)的次數(shù),如果每次阻塞都超時(shí),并且重發(fā)次數(shù)達(dá)到了設(shè)置的上限,則關(guān)閉客戶端。
6、UDP服務(wù)器為所有通信使用同一套接字,這點(diǎn)與TCP服務(wù)器不同,TCP服務(wù)器則為每個(gè)成功返回的accept()方法創(chuàng)建一個(gè)新的套接字。
7、在UDP程序中,DatagramSocket的每一次receive()調(diào)用最多只能接收調(diào)用一次send()方法所發(fā)送的數(shù)據(jù),而且,不同的receive()方法調(diào)用絕對(duì)不會(huì)返回同一個(gè)send()方法所發(fā)送的額數(shù)據(jù)。
8、在UDP套接字編程中,如果receive()方法在一個(gè)緩沖區(qū)大小為n的DatagramPscket實(shí)例中調(diào)用,而接受隊(duì)列中的第一個(gè)消息長(zhǎng)度大于n,則receive()方法只返回這條消息的前n個(gè)字節(jié),超出的其他字節(jié)部分將自動(dòng)被丟棄,而且也沒有任何消息丟失的提示。因此,接受者應(yīng)該提供一個(gè)足夠大的緩存空間的DatagramPacket實(shí)例,以完整地存放調(diào)用receive()方法時(shí)應(yīng)用程序協(xié)議所允許的最大長(zhǎng)度的消息。一個(gè)DatagramPacket實(shí)例中所運(yùn)行傳輸?shù)淖畲髷?shù)據(jù)量為65507個(gè)字節(jié),即UDP數(shù)據(jù)報(bào)文所能負(fù)載的最多數(shù)據(jù),因此,使用一個(gè)有65600字節(jié)左右緩存數(shù)組的數(shù)據(jù)總是安全的。
9、在UDP套接字編程中,每一個(gè)DatagramPacket實(shí)例都包含一個(gè)內(nèi)部消息長(zhǎng)度值,而該實(shí)例一接收到新消息,這個(gè)長(zhǎng)度值便可能改變(以反映實(shí)際接收的消息的字節(jié)數(shù))。如果一個(gè)應(yīng)用程序使用同一個(gè)DatagramPacket實(shí)例多次調(diào)用receive()方法,每次調(diào)用前就必須顯式地將消息的內(nèi)部長(zhǎng)度重置為緩沖區(qū)的實(shí)際長(zhǎng)度。
10、另一個(gè)潛在問題的根源是DatagramPacket類的getData()方法,該方法總是返回緩沖區(qū)的原始大小,忽略了實(shí)際數(shù)據(jù)的內(nèi)部偏移量和長(zhǎng)度信息。
11.TCP是需要連接的,UDP不用連接,是不可靠的傳輸,所以使用UDP要重排亂序消息或者處理丟失消息,因此這就加大了服務(wù)器的負(fù)擔(dān)。但是之所以使用UDP是因?yàn)樵趥鬏斝畔⒑苌贂r(shí),TCP握手花費(fèi)的時(shí)間基本是傳輸?shù)?倍,還有在沒有可靠性要求時(shí),UDP很靈活。
總結(jié)
以上是生活随笔為你收集整理的基本套接字TCP和UDP的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: TCP三次握手及关闭时的2MSL分析
- 下一篇: xgboost之spark上运行-sca