java socket介绍(转)

1. Client/Server Networking
Java通过socket来完成它所有的网络底层的通讯,socket是一种通讯的通道,通过它可以将数据通过特定的端中发送及接收。Java中的socket可以分成二大类:
(1) Datagram Sockets:数据包socket;
(2) Stream Sockets:流socket;
1.2 Datagram Socket
Datagram socket使用UDP来实现数据通讯,因此它不能保证数据能够到达目的地,但是由于它不需要专用的网络链接,所以它所需的资源相对少的多。
Datagram以包的方式发送数据,但它不能保证这些数据包以特定的顺序到达目的,因此包中往往需要包含序列号的信息,接收方可以根据序列号的信息决定是否所有的包都已收到,并按正常顺序重组这些包。
Java通过两个类DatagramSocket和DatagramPacket来支持Datagram socket。DatagramSocket实现了Datagram socket的基本功能,而DatagramPacket则提供了对包的一些支持。
DatagramSocket的几个重要方法:
(1) DatagramSocket():随机绑定一个有效的端口;
(2) DatagramSocket(int port):绑定指定的端口;
(3) Void send(DatagramPacket p):发送数据报,由于目的地的地址信息已包含在数据报中,所以不需要在本函数中提供地址信息;
(4) synchronized void receive(DatagramPacket p):接收数据包,线程安全;
(5) synchronized void close():关闭socket;
在DatagramSocket中并不区分ServerSocket和ClientSocket,如果一定要区分,那么发送的是client,而接收的是server。
DatagramPacket有以几个重要的方法:
(1) DatagramPacket(byte ibuf[], int ilength):用于接收数据报;
(2) DatagramPacket(byte ibuf[], int ilength, InetAddressiaddr, int iport):用于发送的数据报;
(3) byte[] getData()
(4) int getLength()
以下是完整的Datagram Socket的例子:
接收端,Server端代码:
import java.io.*;
import java.net.*;

class FortuneServer1 extends Thread
{
DatagramSocket ServerSocket;
public FortuneServer1()
{
super("FortuneServer1");
try
{
ServerSocket = new DatagramSocket(1114);
System.out.println("FortuneServer up and running...");
}
catch (SocketException e)
{
System.err.println("Exception: couldn't create datagram socket");
System.exit(1);
} }

public static void main(String[] args)
{
FortuneServer1 server = new FortuneServer1();
server.start();
}

public void run()
{
if (ServerSocket == null)
return;

while (true)
{
try
{
InetAddress address;
int port;
DatagramPacket packet;
byte[] data = new byte[256];
int num = 1;

packet = new DatagramPacket(data, data.length);
ServerSocket.receive(packet);
address = packet.getAddress();
port = packet.getPort();

File inFile = new File("Fortunes.txt");
FileInputStream inStream = new FileInputStream(inFile);

if (inStream.read(data) <= 0)
{
System.err.println("Error: couldn't read fortunes");
}

packet = new DatagramPacket(data, data.length, address, port);
ServerSocket.send(packet);
}
catch (Exception e)
{
System.err.println("Exception: " + e);
e.printStackTrace();
} } } }
说明:
(1) 这个例子中接收端的socket使用了多线程,多线程的概念可以参考本文的多线程部分;
(2) 在构造函数中创建了DatagramSocket,并且使用了1114这个端口;
(3) 本例子的关键函数是run,它使用了死循环,在循环中使用了receive这方法来临听1114端口,如果1114端口没有请求数据到来,那么这个程序就一直停留在receive这个位置,不再往下执行;
(4) 当1114端有数据时,receive方法将接收到的DatagramPacket数据包放在packet这个对象中,然后解析其中的数据,获取发送方的地址信息;
(5) 然后再创建DatagramPacket包,并填充数据,发送到原接收端。
以下是发送端的代码:
import java.io.*;
import java.net.*;
class FortuneClient
{
public static void main(String args[])
{
FortuneClient client = new FortuneClient();
System.out.println(client.getMessage());
}

public String getMessage()
{
String fortune;
try {
DatagramSocket socket;
DatagramPacket packet;
byte[] data = new byte[256];

socket = new DatagramSocket();
packet = new DatagramPacket(data, data.length,InetAddress.getByName("127.0.0.1"), 1114);
socket.send(packet);

packet = new DatagramPacket(data, data.length);
socket.receive(packet);
fortune = new String(packet.getData(), 0);
socket.close();
}
catch (UnknownHostException e) {
System.err.println("Exception: host could not be found");
return null;
}
catch (Exception e) {
System.err.println("Exception: " + e);
e.printStackTrace();
return null;
}
return fortune;
} }
客户端的代码与服务器端的代码基本相同,只是没有了循环,并且处理步骤与服务器端的基本相反。这也说明了Datagram Socket不真正区分Server和Client。
1.3 Stream Socket
Stream Socket与Datagram Socket不同,它有一个永久性的链接,可以确保数据可靠的发送到对方。但是Stream Socket所占的资源更多。
Java主要通过Socket及ServerSocket来实现Stream Socket,一个用于客户端,另一个用户服务器端。
Socket类的几个重要方法如下:
(1) Socket(String host, int port)
(2) Socket(InetAddress address, int port)
(3) synchronized void close()
(4) InputStream getInputStream()
(5) OutputStream getOutputStream()
可以看出Stream Socket中的Socket与Datagram Socket中的Socket的区别:
(1) 在构造函数中,这里需要指定服务器的地址与端口,用于它发送数据之前建立链接,而Datagram的Socket不需要建立链接,它仅需要在要发送的数据包中包含地址信息即可;
(2) Stream Socket的两个重要方法和其它流的方法类似,而且在操作上也相似,其实在操作Stream Socket时,只要建立了链接,那么以后的操作和流的操作一样了,你可以忘了你在操作Socket,只当做自己在操作普通的流。
ServerSocket有以下几个重要的方法:
(1) ServerSocket(int port):监听指定端口,监听时间为默认值50;
(2) ServerSocket(int port, int count):监听指定端口,并指定监听时间;
(3) ServerSocket(int port, int backlog, InetAddress bindAddr):只接收指定接口发过来的数据;
(4) Socket accept():accpet方法会返回一个Socket对象,使用该Socket对象的getInputStream及getOutputStream方法获取输入及输出流。由此可见无论是服务器端还是客户端,它们都是通过Socket对象来收发数据的。程序执行到accpet时会进入监听状态,不再向下执行一直到接收到数据。
(5) void close()
以下是关于Stream Socket的一个例子,在这个例子中使用单独的二个类Receiver和Sender负责对指定的输入及输出流进行操作,用另一个类SocketPerformer来创建Receiver和Sender这两个类的实例,并传入指定的输入及输出流,而在Client及Server这两个类中,负责创建SocketPerformer对象,并传入指定的Socket。因此这五个类在功能上分为三层,第一层负责对流进行操作,第二层负责创建指定的Socket流,并这些Socket流传到第一层,第三层负责创建Socket并将这些Socket传到第二层。
Receriver代码:
package socketChat;
import java.io.BufferedReader;
public class Receiver extends Thread {
BufferedReader reader;
public Receiver(BufferedReader theSocketReader) {
reader = theSocketReader;
}

public void run() {
while (true) {
try {
String words = reader.readLine(); System.out.println("\r\n<<< " + words);
} catch (Exception e) {
e.printStackTrace();
return;
} } }}
Sender代码:
package socketChat;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;

public class Sender extends Thread {
PrintWriter writer;
public Sender(PrintWriter theWriter) {
writer = theWriter;
}
public void run() {
try {
while (true) {
BufferedReader consoleReader = new BufferedReader(
new InputStreamReader(System.in));
String userInput = consoleReader.readLine();
writer.write(userInput + "\r\n");
writer.flush(); // send the data in buffer immediately
}
} catch (Exception e) {
e.printStackTrace();
return;
} }}
SocketPerformer代码:
package socketChat;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class SocketPerformer {
Socket socket;
public SocketPerformer(Socket theSoc){
socket = theSoc;
}
public void execut() throws Exception{
BufferedReader socketReader = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
PrintWriter socketWriter = new PrintWriter(socket.getOutputStream());
System.out.println("connection built !");

Receiver rec = new Receiver(socketReader);
rec.start();

Sender sender = new Sender(socketWriter);
sender.start();
}}
Server代码:
package socketChat;

import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws Exception {
ServerSocket server = new ServerSocket(30000, 5);
Socket socket = null;
System.out.println("waiting incoming connection...");
socket = server.accept();
System.out.println("a connection built!");
System.out.println("the peer ip is: " + socket.getInetAddress().getHostAddress());
new SocketPerformer(socket).execut();
server.close();
}}
Client端代码如下:
package socketChat;
import java.net.Socket;
public class Client {

public static void main(String[] args) throws Exception {
String ip = "127.0.0.1";
int port = 30000;
System.out.println("connecting to server: " + ip
+ " at port: " + port + " ...");

Socket soc = new Socket(ip, port);
new SocketPerformer(soc).execut();
}}
2. 其它
(1) 常量定义以final做修饰符,如final int I = 3;final还可以用在class及method上,如果用在class上,那么该class不能被继承,如果用在method上,那么该method不能被重载
(2) 类变量定义:static int sum
(3) 判断对象类型:objName instanceof className,如"helloWorld" instanceof String,判断的结果是true
(4) 提取对象的类名:objName.getClass().getName()
(5) java.lang:Java最基本的包,包含java语言最基本的定义
(6) java.util:包含如Date,及基本的集合类,如Vector、Hashtable等
(7) java.io:包含输入输出的定义
(8) java.net:包含如Socket等
(9) java.awt:用于Window界面开发
(10) java.applet:用于applet开发
(11) null关键字引用null对象,注意大小写
(12) 每个基本的数据类型如int,都有相应的类与之对应中Int
(13) 构造函数名与类名相同,并且不需要返回类型,即在构造函数前不需要加类型修饰符
(14) 调用自身的构造函数:this(参数列表);
(15) 调用父类的构造函数:super(参数列表);
(16) 调用父类已被重载的函数:super.方法名(参数列表);
(17) 在同一文件中可以有多个类的定义,但只能有一个类是public并且类名与文件一样,该文件编译成功后会生成多个.class文件,与类的定义一一对应;
(18) 接口比抽象类功能强有:抽象类属于特定的类结构树中,它不能被其它树中的类共享;
(19) 接口的定义与类的定义类似,只需将class改成interface即可,但接口定义必须是public或者缺省的(package);
(20) 在接口中可以定义方法和变量,接口中的定义的方法应是public并且是abstract的(缺省);变量的定义必须是public static final(缺省);
(21) String.copyValueOf(buffer, 0, offset):用于从字符数组中取出字符,并形成字符串;
(22) new String(buf, 0, count)
(23) System.in(标准输入)是一个InputStream;
(24) System.err是一个PrintStream;
(25) System.out是一个PrintStream;

网络编程
在tcp/ip协议中,ip地址用4个字节,也就是32位的二进制来表示,称为ipv4。

Ip地址只能保证把数据送到该计算机,但不能保证把这些数据交给那个网络程序,因此,每个发送的网络数据包的头部都含有一个被称为“端口”的部分,它是一个整数,用于表示该数据帧交给那个应该程序来处理。还必须为网络应用程序指定一个端口号,计算机上不能有2个使用同一端口的程序运行。

当数据到达第一个网络程序所在的计算机后,驱动程序根据数据包中的端口号,就知道将这个数据包交给这个网络程序。

Udp与tcp

Tcp是面向连接的通信协议,提供2台计算机之间的可靠无差错的数据传输。应用程序利用tcp进行通信时,源和目的直接会建立一个虚拟链接。


Udp是无连接通信协议,udp不保证可靠数据的传输,但能够向若干个目标发送数据,接收发自若干个源的数据。

数据包的基本格式:

协议类型
源ip
目的ip
源端口
目的端口
帧序号
帧数据


其中协议类型用于区分tcp和udp。


Socket

Socket是网络驱动层提供给应用程序的接口和一种机制。

可以认为socket是应用程序创建的一个港口码头,应用程序只要把装着货物的集装箱(要发送的数据)放在码头上,就算完成了货物的运送,剩下的工作就由货运公司(驱动程序)去处理了。

对接收方来说,应用程序也要创建一个码头,然后就一直等待该码头的货物到达,最后从码头上取走货物(数据)。

Socket在应用程序中创建,通过一个绑定机制与驱动程序建立关系,告诉自己所对应的ip和port。此后,应用程序送给socket的数据,由socket交给驱动程序向网络上发送。计算机从网络收到与该socket绑定的ip+port相关数据后,由驱动程序交给socket,应用程序便可以从该sokcet中取得接收到的数据。

Java分别为udp和tcp2种通信协议提供了相应的编程类,存放在java.net包中,与udp对应的是DatagramSocket,与tcp对应的是ServerSocket(用于服务器)和Socket(用于客户端)。


Java编写udp网络程序

Java.net.DategramSocket类 的构造函数主要有以下几种:

Public DatagramSocket() throws SocketException

Public DatagramSocket(int port) throws SocketException

Public DatagramSocket(int port,InetAddress laddr) throws SocketException

编写发送程序时,我们可以用第一个构造函数(创建DatagramSocket对象时,不指定端口号,系统就会为我们分配一个端口号)。

注意:第三个构造函数创建DatagramSocket对象,除了指定自己想要的端口号外,还可以指定相关的ip地址,这个情况适合计算机上有多个网卡和多个ip的情况。其实对于只有一块网卡的情况,如果在这里指定了ip地址,反而会给程序带来很大的不方便,因为这个网络程序只能在具有这个ip的计算机上运行,而不能在其他计算机上面运行。

编写接收程序时,我们必须自己指定一个端口号,而不让系统随即分配,可以使用第二个构造函数。

如果程序不在使用某个sokcet,应该调用DatagramSocket.close()方法,关闭这个socket,通知驱动程序释放为这个socket所保留的资源,系统就可以将这个socket所占用的端口号重新分配给其他程序使用。

在发送数据时,我们用Datagram.send()方法,

public void send(DatagramPacket p) throws IOException


在接收数据时,我们用Datagram.receice()方法,
public void receive(DatagramPacket p) throws IOException

这2个方法都需要传递一个DatagramPacket类的实例对象,如果把DatagramSocket比作创建的码头,那么DatagramPacket就是我们发送和接收数据的集装箱。

DatagramPacket类构造函数主要有:

public DatagramPacket(byte[] buf,int length)


public DatagramPacket(byte[] buf, int length, InetAddress address, int port)


在接收数据时,我们应该用第一个构造函数来创建接收数据的DatagramPacket对象。
在发送数据时,我们必须指定接收方socket的地址和端口号,所以我们应该用第二个构造函数来创建发送数据的DatagramPacket对象。

最简单的udp程序:接收应用程序的端口号为3000。发送程序的端口号由系统分配。
import java.io.IOException;

import java.net.*;


public class UdpSend


{


public static void main(String [] args)


{


DatagramSocket ds = null;


try { ds = new DatagramSocket();


} catch (SocketException e) {


e.printStackTrace();


}


String str="hello world";


DatagramPacket dp = null;


try {dp = new DatagramPacket(str.getBytes(),str.length(),InetAddress.getByName("192.168.1.101"),3000);

} catch (UnknownHostException e1) {


e1.printStackTrace();

}

try {ds.send(dp);

} catch (IOException e2) {

e2.printStackTrace();


}


ds.close();


}


}
import java.io.IOException;
import java.net.*;
public class UdpRecv
{

public static void main(String [] args )

{


DatagramSocket ds=null;


try{ds=new DatagramSocket(3000);}


catch(SocketException e){e.printStackTrace();}


byte[] buf=new byte[1024];





DatagramPacket dp=null;


dp=new DatagramPacket(buf,1024);

try { ds.receive(dp);


} catch (IOException e1) { // TODO Auto-generated catch block


e1.printStackTrace();


}
String str=new String(dp.getData(),0,dp.getLength()) + " from " + dp.getAddress().getHostAddress() + ":" +dp.getPort();
//public String(byte[] byte)是将数组中的所有元素都转换成字符串,包括哪些没有填充的单元
// public String(byte[] bytes,int offset,int length)是将字节数组中从offset开始,往后一共length
//个单元的内容转换成字符串


System.out.println(str);


ds.close();


}
}

Java编写tcp网络程序:

利用tcp协议进行通信的2个应用程序,是有主次之分的。一个称为服务器程序,另外一个称为客户机程序。2者的能够和编写方法大不一样。

服务器和客户端交互过程:

1、服务器程序创建一个ServerSocket,然后调用accept方法等待客户来链接。

2、客户端程序创建一个Socket并请求与服务器建立连接。

3、服务器接收客户的连接请求,并创建一个新的sokcet与该客户建立专线连接。

4、刚才建立了连接的2个socket在一个单独的线程(由服务器程序创建)上对话。

5、服务器开始等待新的连接请求。

ServerScoket

构造方法:
public ServerSocket() throws IOException

public ServerSocket(int port) throws IOException

public ServerSocket(int port,int backlog) throws IOException

public ServerSocket(int port,int backlog,InetAddress bindAddr) throws IOException

第一个:没有与任何端口号绑定,不能直接使用,还要调用bind方法才能完成构造函数所完成的工作。

第二个:创建serverSocket对象,可以将这个serverSocket绑定到一个指定的端口上,作为服务器程序,端口号必须事先指定,其他客户才能根据这个号码进行连接。所以将端口号指定为0的情况并不常见。

第三个:创建serverSocket对象,就是在第二个构造函数的基础上,我们根据backlog参数指定,在服务器忙时,可以与之保持连接请求的等待客户数量,对于第二个构造函数,没有指定这个参数,则使用默认的数量,大小为50。


第四个:创建serverSocket对象,除了指定第三个构造函数中的参数外,还可以指定相关的ip地址,这种情况适用于计算机上有多个网卡和多个ip的情况。对于一般情况只有一块网卡的情况,就不用指定ip了。

第二个构造方法比较适合创建我们的serversocket对象。

Socket

客户端于服务器建立连接,首先必须创建一个sokcet对象,它的构造方法:

public Socket()
public Socket(String host,int port)throws UnknownHostException,IOException


public Socket(InetAddress address,int port) throws IOException


public Socket(String host,int port,InetAddress localAddr,int localPort) throws IOException


public Socket(InetAddress address,int port,InetAddress localAddr, int localPort) throws IOException

第一个:能够创建sokcet对象,不与任何服务器建立连接,不能直接使用,还要调用connect方法才能完成构造函数所完成的工作。


第二个:创建socket对象后,会根据参数去连接在特定地址和端口上运行的服务器程序,接收字符串格式的地址。


第三个:创建socket对象后,会根据参数去连接在特定地址和端口上运行的服务器程序,接收InetAddress对象所包装的地址。


第四个和第五个:在第二个和第三个构造函数的基础上,还指定了本地sokcet所绑定的ip地址和端口号,由于客户端的端口号的选择不重要,所以一般情况下,我们不会使用这2个构造函数。
我们选择第二个构造函数来创建客户端的socket对象并与服务器建立连接。

服务器端程序调用serversokcet.accept方法等待客户的连接请求,一旦accept接收了客户连接请求,该方法将返回一个与该客户建立专线连接的sokcet对象,不用程序去创建这个socket对象。当客户端和服务器端的2个sokcet建立了专线连接后,连接的一端能向另外一端连续写入字节,也能从另外一端连续读入字节,就是建立了专线连接的2个sokcet是以io流的方式进行数据交换的。Java提供了sokcet.getInputStream方法返回socket的输入流对象,socket..getOutputstrram方法返回sokcet的输出流对象。只要连接的一端向该输出流对象写入了数据,连接的另一端就能从输入流对象这读取到这些数据。

简单的服务器程序:
import java.net.*;

import java.io.*;
public class TcpServer {


public static void main(String[] args) {


try{


ServerSocket ss=new ServerSocket(8001);


Socket s=ss.accept();


InputStream ips=s.getInputStream();


OutputStream ops=s.getOutputStream();


ops.write("welcome to www!".getBytes());//通过输出流首先向客户端发送一串字符


byte [] buf=new byte[1024];


int len=ips.read(buf);//通过输入流读取客户端发来的信息,并保存到一个字节数组中


System.out.println(new String(buf,0,len));


ips.close();


ops.close();


s.close();


ss.close();


}


catch(Exception e) {e.printStackTrace();}


}


}
上面的代码如果在telnet中输入一个字母,就会发送到服务器端,马上就退出了。为了实现客户端输入一行字母,代码修改如下:

import java.net.*;

import java.io.*;
public class TcpServer {
public static void main(String[] args) {


try{


ServerSocket ss=new ServerSocket(8001);


Socket s=ss.accept();


InputStream ips=s.getInputStream();


OutputStream ops=s.getOutputStream();

ops.write("welcome to www!".getBytes());//通过输出流首先向客户端发送一串字符


BufferedReader br=new BufferedReader(new InputStreamReader(ips));;

//byte [] buf=new byte[1024];


//int len=ips.read(buf);//通过输入流读取客户端发来的信息,并保存到一个字节数组中


//System.out.println(new String(buf,0,len));


System.out.println(br.readLine());

//ips.close();


br.close();//关闭包装类,会自动关闭包装类中所包装的底层类。所以不用调用ips.close()


ops.close();


s.close();


ss.close();


}


catch(Exception e)


{e.printStackTrace();}


}


}


完善的tcp服务器程序模型:

import java.io.*;

import java.net.*;

public class Servicer implements Runnable{
Socket s;

public Servicer(Socket s)

{


this.s=s;


}


public void run()


{


try{


InputStream ips=s.getInputStream();


OutputStream ops=s.getOutputStream();


BufferedReader br=new BufferedReader(new InputStreamReader(ips));


DataOutputStream dos=new DataOutputStream(ops);


while(true)


{


String str=br.readLine();


if(str.equalsIgnoreCase("quit"))


break;


String strEcho=(new StringBuffer(str)).toString();


//String strEcho=(new StringBuffer(str).reverse()).toString();


dos.writeBytes(str + "--->" + strEcho + System.getProperty("line.separator"));


}

br.close();

dos.close();


s.close();


}


catch(Exception e){e.printStackTrace();}


}


}


class TcpServer

{
public static void main(String [] args)


{


try{


ServerSocket ss=new ServerSocket(8001);


while(true)


{
Socket s=ss.accept();

new Thread(new Servicer(s)).start();

}
}

catch(Exception e1){e1.printStackTrace();}

}


}

import java.net.*;
import java.io.*;

public class TcpClient {
public static void main(String[] args) {

try{
if(args.length<2)

{

System.out.println("Usage:java Tcpclient serverIP serverPort");


return;


}


Socket s=new Socket(InetAddress.getByName(args[0]),Integer.parseInt(args[1]));


InputStream ips=s.getInputStream();


OutputStream ops=s.getOutputStream();

BufferedReader brkey=new BufferedReader(new InputStreamReader(System.in));
DataOutputStream dos=new DataOutputStream(ops);
BufferedReader brnet=new BufferedReader(new InputStreamReader(ips));



while(true)

{

String strW=brkey.readLine();


dos.writeBytes(strW + System.getProperty("line.separator"));


if (strW.equalsIgnoreCase("quit"))


break;


else


System.out.println(brnet.readLine());


}


dos.close();


brnet.close();


brkey.close();


s.close();


}


catch(Exception e){e.printStackTrace();

}


}

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值