http://jimmee.iteye.com/blog/617110
http://jimmee.iteye.com/category/93740
Java TCP/IP Socket 编程 笔记(一)—基本概念
编程 Socket Java 网络协议 网络应用
一些概念:
通信信道(communication channel):将字节序列从一个主机传输到另一个主机的一种手段,可能是有线电缆,如以太网(Ethernet),也可能是无线的,如WiFi,或是其他方式的连接。
信息(information)是指由程序创建和解释的字节序列。在计算机网络环境中,这些字节序列称为分组报文(packet)。
协议(protocol)相当于相互通信的程序达成的一种约定,它规定了分组报文的交换方式和它们包含的意义。一组协议规定了分组报文的结构(例如报文中的哪一部分表明了目的地址)以及怎样对报文中所包含的信息进行解析。
TCP和UDP属于传输层,IP属于网络层,TCP,UDP和IP的具体实现通常驻留在主机的操作系统中。应用程序通过套接字API对UDP协议和TCP协议所提供的服务进行访问。
IP协议提供了一种数据报服务:每组分组报文都由网络独立处理和分发,就像信件或包裹通过邮政系统发送一样。IP报文必须包含一个保存其目的地址的字段,就像你所投递的每份包裹都写明了收件人地址一样。
TCP协议和UDP协议使用的地址叫做端口号,都是用来区分同一主机中的不同应用程序的。
客户端(client)是通信的发起者,而服务器(server)程序则被动等待客户端发起通信,并对其作出响应。
一个程序是作为客户端还是服务器,决定了它在与其对等端(peer)建立通信时使用的套接字API(客户端的对等端是服务器,反之亦然)。客服端必须首先知道服务器端的地址和端口号,反之则不需要。这个打电话类似。只要通信连接建立成功,服务器和客户端之间就没有区别了。
Socket(套接字)是一种抽象层,应用程序通过它来发送和接受数据,就像应用程序打开一个文件句柄,将数据读写到稳定的存储器上一样。一个TCP/IP套接字由一个互联网地址,一个端对端协议(TCP或UDP协议)以及一个端口号唯一确定。
Java TCP/IP Socket 编程 笔记(二)—TCP的例子
编程 Socket Java 网络协议
1.InetAddress类和SocketAddress用于识别网络主机
TCP协议客户端和服务器端的套接字为Socket和ServerSocket
UDP协议的客户端和服务器端的套接字为DatagramSocket
2.
类 NetworkInterface表示一个由名称和分配给此接口的 IP 地址列表组成的网络接口,其getNetworkInterfaces()返回此机器上的所有接口。getInetAddresses()是返回一个 Enumeration 并将所有 InetAddress 或 InetAddress 的子集绑定到此网络接口的便捷方法。(注意:一个网络接口可能包含IPv4或IPv6地址)
3.类 InetAddress的getHostAddress()返回 IP 地址字符串(以文本表现形式)。 getAllByName(String host)在给定主机名的情况下,根据系统上配置的名称服务返回其 IP 地址所组成的数组。getHostName()获取此 IP 地址的主机名。getHostAddress() 返回 IP 地址字符串(以文本表现形式)。
4.TCP套接字
服务器端ServerSocket实例监听TCP链接请求,并为每个请求创建新的Socket实例。也就是说,服务器端要同时处理ServerSocket实例和Sockete实例,而客户端只需要使用Socket实例。
TCP客户端:
- public class TCPEchoClient {
- public static void main(String [] args) throws UnknownHostException, IOException, InterruptedException {
- if(args.length<2||args.length>3){
- throw new IllegalArgumentException("Parameter(s):<Server> <Word> [<Port>]");
- }
-
- String server=args[0];
-
- byte [] data=args[1].getBytes();
-
- int servPort=(args.length==3)?Integer.parseInt(args[2]):7;
-
-
- Socket socket=new Socket(server,servPort);
- System.out.println("Connected to server... sending echo string");
-
-
-
-
- InputStream in=socket.getInputStream();
- OutputStream out=socket.getOutputStream();
-
- out.write(data);
-
- int totalBytesRcvd=0;
- int bytesRcvd;
-
- while(totalBytesRcvd<data.length){
- if((bytesRcvd=in.read(data, totalBytesRcvd, data.length-totalBytesRcvd))==-1){
- throw new SocketException("Connection closed prematurely");
- }
- totalBytesRcvd+=bytesRcvd;
- }
- System.out.println("Receved: "+new String(data));
-
-
- socket.close();
- }
- }
public class TCPEchoClient {
public static void main(String [] args) throws UnknownHostException, IOException, InterruptedException {
if(args.length<2||args.length>3){
throw new IllegalArgumentException("Parameter(s):<Server> <Word> [<Port>]");
}
String server=args[0];
byte [] data=args[1].getBytes();
int servPort=(args.length==3)?Integer.parseInt(args[2]):7;
//1.创建一个Socket实例:构造函数向指定的远程主机和端口建立一个TCP连接
Socket socket=new Socket(server,servPort);
System.out.println("Connected to server... sending echo string");
/**
*2. 通过套接字的输入输出流进行通信:一个Socket连接实例包括一个InputStream和一个OutputStream,它们的用法同于其他Java输入输出流。
*/
InputStream in=socket.getInputStream();
OutputStream out=socket.getOutputStream();
out.write(data);
int totalBytesRcvd=0;
int bytesRcvd;
while(totalBytesRcvd<data.length){
if((bytesRcvd=in.read(data, totalBytesRcvd, data.length-totalBytesRcvd))==-1){
throw new SocketException("Connection closed prematurely");
}
totalBytesRcvd+=bytesRcvd;
}
System.out.println("Receved: "+new String(data));
//3.使用Socet类的close()方法关闭连接
socket.close();
}
}
TCP服务器端代码:
- public class TCPEchoServer {
- private static final int BUFSIZE=32;
-
- public static void main(String [] args) throws IOException, InterruptedException{
- if(args.length!=1){
- throw new IllegalArgumentException("Parameter(s):<Port>");
- }
-
- int servPort=Integer.parseInt(args[0]);
-
-
- ServerSocket servSock=new ServerSocket(servPort);
-
- int recvMsgSize;
-
- byte [] receiveBuf=new byte[BUFSIZE];
-
-
- while(true){
-
-
- Socket clntSock=servSock.accept();
- SocketAddress clientAddress=clntSock.getRemoteSocketAddress();
- System.out.println("Handling client at "+clientAddress);
-
-
- InputStream in=clntSock.getInputStream();
- OutputStream out=clntSock.getOutputStream();
-
- while((recvMsgSize=in.read(receiveBuf))!=-1){
- out.write(receiveBuf, 0, recvMsgSize);
- }
-
-
- clntSock.close();
- }
- }
- }
Java TCP/IP Socket 编程 笔记(三)—UDP的例子
1.UDP套接字与TCP套接字不同。UDP套接字在使用前不需要进行连接。TCP协议与电话通信相似,而UDP协议则与邮件通信相似:你寄包裹或信件时不要进行“连接”,但是你的为每个包裹和信件制定目的地址。类似地,每条信息(datagram,即数据报文)负载了自己的地址信息,并与其他信息相互独立。在接收信息时,UDP套接字扮演的角色就像是一个信箱,从不同地址发送来的信件和包裹都可以放到里面。一旦被创建,UDP套接字就可以用来连续地向不同的地址发送消息,或从任何地址接收信息。
UDP套接字将保留边界信息。UDP不像TCP一样,它是尽可能地传送消息,但并不保证信息一定能成功到达目的地址,而且信息到达的顺序与其发送顺序不一定一致(就像通过邮政部分寄信一样)。因此,UDP套接字的程序必须准备好处理信息的丢失和重排。
UDP的优点之一是效率较高,其次是灵活性。
Java通过DatagramPacket类和DatagramSocket类来使用UDP套接字。客户端和服务端都使用DatagramSocket来发送数据,使用DatagramPacket来接收数据。
发送信息时,Java程序创建一个包含了待发送信息的DatagramPacket实例,并将其作为参数传递给DatagramSocket类的send()方法。接收信息时,Java程序首先创建一个DatagramPacket类的实例,该实例中预先分配了一些空间(一个字节数组byte[]),并将接收到的信息存放在该空间中。然后把该实例作为参数传递给DatagramSocket类的receive()方法。
DatagramPacket的内部有length和offset字段,如果指定了offset,数据报文的数据部分将从字节数组的指定位置发送或接收数据。length参数指定了字节数组中在发送时要传输的字节数,活在接收数据时所能接收的最多字节数。length要比data.length小,但不能比它大。
UDP客户端:
- import java.io.IOException;
- import java.io.InterruptedIOException;
- import java.net.DatagramPacket;
- import java.net.DatagramSocket;
- import java.net.InetAddress;
-
- public class UDPEchoClientTimeout {
- private static final int TIMEOUT=3000;
- private static final int MAXTRIES=5;
-
- public static void main(String[] args) throws IOException {
- if(args.length<2||args.length>3){
- throw new IllegalArgumentException("Parameter(s):<Server> <Word> [<Port>]");
- }
-
- InetAddress serverAddress=InetAddress.getByName(args[0]);
- byte [] bytesToSend=args[1].getBytes();
- int servPort=(args.length==3)?Integer.parseInt(args[2]):7;
-
-
- DatagramSocket socket=new DatagramSocket();
-
- socket.setSoTimeout(TIMEOUT);
-
- DatagramPacket sendPacket=new DatagramPacket(bytesToSend,bytesToSend.length,serverAddress,servPort);
- DatagramPacket receivePacket=new DatagramPacket(new byte[bytesToSend.length],bytesToSend.length);
-
- int tries=0;
- boolean receivedResponse=false;
-
- do{
-
- socket.send(sendPacket);
- try{
- socket.receive(receivePacket);
- if(!receivePacket.getAddress().equals(serverAddress)){
- throw new IOException("Received packet from an unknown source");
- }
- receivedResponse=true;
- }catch(InterruptedIOException e){
- tries+=1;
- System.out.println("Timed out,"+(MAXTRIES-tries)+" more tries ...");
- }
-
- }while(!receivedResponse&&(tries<MAXTRIES));
-
-
- if(receivedResponse){
- System.out.println("Received: "+new String(receivePacket.getData()));
- }else{
- System.out.println("No response -- giving up.");
- }
-
-
- socket.close();
- }
- }
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPEchoClientTimeout {
private static final int TIMEOUT=3000;
private static final int MAXTRIES=5;
public static void main(String[] args) throws IOException {
if(args.length<2||args.length>3){
throw new IllegalArgumentException("Parameter(s):<Server> <Word> [<Port>]");
}
InetAddress serverAddress=InetAddress.getByName(args[0]);//server address;
byte [] bytesToSend=args[1].getBytes();
int servPort=(args.length==3)?Integer.parseInt(args[2]):7;
//1.创建一个DatagramSocket实例,可以选择对本地地址和端口进行设置。
DatagramSocket socket=new DatagramSocket();
//设置receive()方法的最长阻塞时间
socket.setSoTimeout(TIMEOUT);
DatagramPacket sendPacket=new DatagramPacket(bytesToSend,bytesToSend.length,serverAddress,servPort);
DatagramPacket receivePacket=new DatagramPacket(new byte[bytesToSend.length],bytesToSend.length);
int tries=0;
boolean receivedResponse=false;
do{
//2.使用DatagramSocket类的send()和receive()方法来发送和接收DatagramPacket实例,进行通信
socket.send(sendPacket);
try{
socket.receive(receivePacket);
if(!receivePacket.getAddress().equals(serverAddress)){
throw new IOException("Received packet from an unknown source");
}
receivedResponse=true;
}catch(InterruptedIOException e){
tries+=1;
System.out.println("Timed out,"+(MAXTRIES-tries)+" more tries ...");
}
}while(!receivedResponse&&(tries<MAXTRIES));
if(receivedResponse){
System.out.println("Received: "+new String(receivePacket.getData()));
}else{
System.out.println("No response -- giving up.");
}
//3.通信完成后,使用DatagramSocket类的close方法来销毁该套接字
socket.close();
}
}
UDP的服务器端:
- import java.io.IOException;
- import java.net.DatagramPacket;
- import java.net.DatagramSocket;
-
- public class UDPEchoServer {
- private static final int ECHOMAX=255;
-
- public static void main(String[] args) throws IOException {
- if(args.length!=1){
- throw new IllegalArgumentException("Parameter(s):<Port>");
- }
-
- int servPort=Integer.parseInt(args[0]);
-
-
- DatagramSocket socket=new DatagramSocket(servPort);
- DatagramPacket packet=new DatagramPacket(new byte[ECHOMAX],ECHOMAX);
-
- while(true){
-
- socket.receive(packet);
- System.out.println("Handling client at "+packet.getAddress().getHostAddress()+" on port "+packet.getPort());
-
- socket.send(packet);
- packet.setLength(ECHOMAX);
- }
- }
- }
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPEchoServer {
private static final int ECHOMAX=255;//max size of echo datagram
public static void main(String[] args) throws IOException {
if(args.length!=1){
throw new IllegalArgumentException("Parameter(s):<Port>");
}
int servPort=Integer.parseInt(args[0]);
//1.创建一个DatagramSocket实例,指定本地端口号,可以选择指定本地地址
DatagramSocket socket=new DatagramSocket(servPort);
DatagramPacket packet=new DatagramPacket(new byte[ECHOMAX],ECHOMAX);
while(true){
//2.使用DatagramSocket的receive方法来接收一个DatagramPacket实例。
socket.receive(packet);
System.out.println("Handling client at "+packet.getAddress().getHostAddress()+" on port "+packet.getPort());
socket.send(packet);
packet.setLength(ECHOMAX);
}
}
}
注意:DatagramPacket的getData()方法的使用,它返回数据缓冲区,是一个字节数组,需要注意。
packet.setData(buf, offset,length);设置了接收数据时放到缓存去buf中的位置
,因此接收的数据new String(packet.getData(),packet.getOffset(),packet.getLength())的方式构造的。
Java TCP/IP Socket 编程 笔记(四)—发送和接收数据
编程 Java Socket
1.TCP/IP协议要求信息必须在块(chunk)中发送和接收,而块的长度必须是8位的倍数,因此,我们可以认为TCP/IP协议中传输的信息是字节序列。如何发送和解析信息需要一定的应用程序协议。
2.信息编码:
首先是Java里对基本整型的处理,发送时,要注意:1)每种数据类型的字节个数;2)这些字节的发送顺序是怎样的?(little-endian还是big-endian);3)所传输的数值是有符号的(signed)还是无符号的(unsigned)。具体编码时采用位操作(移位和屏蔽)就可以了。具体在Java里,可以采用DataOutputStream类和ByteArrayOutputStream来实现。恢复时可以采用DataInputStream类和ByteArrayInputStream类。
其次,字符串和文本,在一组符号与一组整数之间的映射称为编码字符集(coded character set)。发送者与接收者必须在符号与整数的映射方式上达成共识,才能使用文本信息进行通信,最简单的方法就是定义一个标准字符集。具体编码时采用String的getBytes()方法。
最后,位操作。如果设置一个特定的设为1,先设置好掩码(mask),之后用或操作;要清空特定一位,用与操作。
3.成帧与解析
成帧(framing)技术解决了接收端如何定位消息的首位位置的问题。
如果接收者试图从套接字中读取比消息本身更多的字节,将可能发生以下两种情况之一:如果信道中没有其他消息,接收者将阻塞等待,同时无法处理接收到的消息;如果发送者也在等待接收端的响应消息,则会形成死锁(dealock);另一方面,如果信道中还有其他消息,则接收者会将后面消息的一部分甚至全部读到第一条消息中去,这将产生一些协议错误。因此,在使用TCP套接字时,成帧就是一个非常重要的考虑因素。
有两个技术:
1. 基于定界符(Delimiter-based):消息的结束由一个唯一的标记(unique marker)指出,即发送者在传输完数据后显式添加的一个特殊字节序列。这个特殊标记不能在传输的数据中出现。幸运的是,填充(stuffing)技术能够对消息中出现的定界符进行修改,从而使接收者不将其识别为定界符。在接收者扫描定界符时,还能识别出修改过的数据,并在输出消息中对其进行还原,从而使其与原始消息一致。
2. 显式长度(Explicit length):在变长字段或消息前附加一个固定大小的字段,用来指示该字段或消息中包含了多少字节。这种方法要确定消息长度的上限,以确定保存这个长度需要的字节数。
接口:
- import java.io.IOException;
- import java.io.OutputStream;
-
- public interface Framer {
- void frameMsg(byte [] message,OutputStream out) throws IOException;
- byte [] nextMsg() throws IOException;
- }
import java.io.IOException;
import java.io.OutputStream;
public interface Framer {
void frameMsg(byte [] message,OutputStream out) throws IOException;
byte [] nextMsg() throws IOException;
}
定界符的方式:
- import java.io.ByteArrayOutputStream;
- import java.io.EOFException;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
-
- public class DelimFramer implements Framer {
- private InputStream in;
- private static final byte DELIMTER=(byte)'\n';
-
- public DelimFramer(InputStream in){
- this.in=in;
- }
-
- @Override
- public void frameMsg(byte[] message, OutputStream out) throws IOException {
-
- for(byte b:message){
- if(b==DELIMTER)
- throw new IOException("Message contains delimiter");
- }
- out.write(message);
- out.write(DELIMTER);
- out.flush();
-
-
- }
-
- @Override
- public byte[] nextMsg() throws IOException {
- ByteArrayOutputStream messageBuffer=new ByteArrayOutputStream();
- int nextByte;
-
- while((nextByte=in.read())!=DELIMTER){
- if(nextByte==-1){
- if(messageBuffer.size()==0){
- return null;
- }else{
- throw new EOFException("Non-empty message without delimiter");
- }
- }
- messageBuffer.write(nextByte);
- }
- return messageBuffer.toByteArray();
- }
- }
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class DelimFramer implements Framer {
private InputStream in;//data source;
private static final byte DELIMTER=(byte)'\n';//message delimiter
public DelimFramer(InputStream in){
this.in=in;
}
@Override
public void frameMsg(byte[] message, OutputStream out) throws IOException {
//ensure that the message dose not contain the delimiter
for(byte b:message){
if(b==DELIMTER)
throw new IOException("Message contains delimiter");
}
out.write(message);
out.write(DELIMTER);
out.flush();
}
@Override
public byte[] nextMsg() throws IOException {
ByteArrayOutputStream messageBuffer=new ByteArrayOutputStream();
int nextByte;
while((nextByte=in.read())!=DELIMTER){
if(nextByte==-1){//end of stream?
if(messageBuffer.size()==0){
return null;
}else{
throw new EOFException("Non-empty message without delimiter");
}
}
messageBuffer.write(nextByte);
}
return messageBuffer.toByteArray();
}
}
显式长度方法:
- import java.io.DataInputStream;
- import java.io.EOFException;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
-
- public class LengthFramer implements Framer {
- public static final int MAXMESSAGELENGTH=65535;
- public static final int BYTEMASK=0xff;
- public static final int SHOTMASK=0xffff;
- public static final int BYTESHIFT=8;
-
- private DataInputStream in;
-
- public LengthFramer(InputStream in) throws IOException{
- this.in=new DataInputStream(in);
- }
-
- @Override
- public void frameMsg(byte[] message, OutputStream out) throws IOException {
- if(message.length>MAXMESSAGELENGTH){
- throw new IOException("message too long");
- }
-
-
- out.write((message.length>>BYTEMASK)&BYTEMASK);
- out.write(message.length&BYTEMASK);
-
- out.write(message);
- out.flush();
- }
-
- @Override
- public byte[] nextMsg() throws IOException {
- int length;
-
- try{
- length=in.readUnsignedShort();
- }catch(EOFException e){
-
- return null;
- }
-
- byte [] msg=new byte[length];
- in.readFully(msg);
- return msg;
- }
- }