网络编程的基础知识
本地回路地址:127.0.0.1.
同一计算机中不能有两个应用程序有相同的端口号,端口号范围0—65535,0—1023之间的端口数是用于一些知名的网络服务与应用。
Socket 是 网络驱动层 提供给应用程序编程的接口和一种机制,在应用程序中创建,通过绑定机制与驱动程序建立关系,并告知自己所对应的ip和port,为应用程序的“码头”。在传送方,只需将数据放到这个码头,就可以了,剩下的由驱动程序完成。在接收方,也有自己的驱动程序接收数据,然后传给“码头”。接收方会不断查询“码头”。
Socket数据发送过程:
Socket数据接受过程:
DatagramSocket类用于UDP通信
ServerSocket 类用于TCP通信的服务器端。(接受客户端的连接)
Socket类用于TCP通信的服务器和客户端(专门用于数据传输)
如果在创建Socket的时候,没有指定端口号,系统会为它分配一个未使用的端口号。
如果在创建Socket的时候,没有指定IP地址,那么底层驱动程序会从计算机上选择任一个IP地址作为发送源的IP。若一个计算机只有一个IP,则不需指定了。
UDP编程
DatagramSocket类
1、构造函数:
public DatagramSocket() 不指明IP地址和端口号,用于UDP程序不先接收数据而是先发送数据时,此时系统会为它分配一个未使用的端口号。
public DatagramSocket(int port) 用于UDP程序先接受数据时
public DatagramSocket(int port,InetAddress laddr) 用于UDP程序运行的主机中有多个IP地址时,需指明UDP程序接受和发送数据使用的IP地址
2 close方法 通知驱动程序释放相应的资源,原程序所用的端口号可以分配给其他的程序
3 send(DatagrmPacket p)方法用于发送UDP数据包
4 receive(DatagramPacket p)方法接受发送UDP数据包
DatagramPacket类
如果把DatagramSocket比作创建的港口码头,那么DatagramPacket就是发送和接收数据的集装箱。
主要构造函数:
public DatagramPacket(byte[] buf,int length) 两个参数分别指明了数据包的缓冲区及缓冲区的大小。此方法用于接收数据时。
public DatagramPacket(byte[] buf,int length,InetAddress address,int port)前两个参数意义同上,后两个参数分别指明了接收数据包的目标程序的IP地址及端口。此方法用于发送数据时。并且buf中必须含有要发送的数据。
当用于接收数据的DatagramPacke对象接受到网络上发送过来的数据以后,对方的IP地址及端口号也是包含在DatagramPacke对象当中的,使用DatagramPacke的getInetAddress和getPort方法就可以获得发送方的端口号和IP地址。
当创建接收数据包的DatagramPacke对象时,因为无法预知接受的数据大小,都是在程序中预先定义一个相对足够大的字节数组作为缓冲区,传递给DatagramPacke的构造函数。getData方法就是返回DatagramPacke对象的字节数组缓冲区,getLength方法返回DatagramPacke对象实际接收到的数据的长度。
另外只有UDP程序才能接受和发送广播数据(将IP地址的主机号每位都设为1)
InetAddress类
InetAddress是用于表示计算机IP地址的一个类。
getByName方法 :是InetAddress的一个静态方法,可以根据网络地址字符串(如“www.it325.org”)来返回相应的InetAddress实例对象。
getHostAddress方法:可以返回用点分割的数字表示的IP地址。(返回字符串格式)
当UDP接受程序调用receive方法接收数据时,如果Socket缓冲区中没有数据到达,该方法会一直处于堵塞状态,直到有数据到达。
而UDP发送程序调用send方法发送数据时,当接受程序没有工作时仍发送,会导致数据流失。因此应先让UDP接受程序运行。
在命令行中输入start,会产生一个新的命令行窗口,该窗口完全继承原窗口的环境。
变成举例:
import java.net.*;
public class UdpSend {
public static void main(String[] args) {
DatagramSocket ds=null;
String str="hello java";//要发送的数据
try
{
ds=new DatagramSocket();
ds.send(new DatagramPacket(str.getBytes(),
str.length(),InetAddress.getByName("127.0.0.1"),3000));
}
catch(Exception e)
{
}
ds.close();
}
}
import java.net.*;
public class UdpReveive {
public static void main(String[] args) throws Exception{
DatagramSocket ds=new DatagramSocket(3000);
byte[] buf=new byte[1024];
DatagramPacket dp=new DatagramPacket(buf,1024);
ds.receive(dp);
String str=new String(dp.getData(),0,dp.getLength());
System.out.println(str+" from: "+dp.getAddress().getHostAddress()+
" "+dp.getPort() );
ds.close();
}
}
UDP小聊天程序
import java.awt.*;
import java.awt.event.*;
import java.net.*;
public class Chat extends Frame{
List lst=new List(6);
//参数表示列表框中最多显示6行,超过6行用滚动条表示。
TextField tfIP=new TextField(15);
TextField tfData=new TextField(20);
DatagramSocket ds=null;
public Chat(String s)
{
super(s);
try
{
ds=new DatagramSocket(3000);
}
catch(Exception e)
{
e.printStackTrace();
}
this.add(lst,"Center"); Panel p=new Panel();
p.setLayout(new BorderLayout());p.add(tfIP,"West"); p.add(tfData,"East");
this.add(p,"South"); this.setSize(300,350); this.setResizable(false);
new Thread(new Runnable()
/*在一个新的线程中启动接收过程,因为在接收不到时会堵塞,
可以避免整个程序堵塞。*/
{
public void run()
{
byte[] buf=new byte[1024];
DatagramPacket dp=new DatagramPacket(buf,1024);
while(true)
{
try
{
ds.receive(dp);
String str=new String(buf,0,dp.getLength());
String strIP=dp.getAddress().getHostAddress();
lst.add(str+" from "+strIP+":"+dp.getPort(), 0);
/*第二个参数表示插入的位置在第一行,无此参数则表示新加入的在
* 最下面。*/
}
catch(Exception e)
{
if(!ds.isClosed())
e.printStackTrace();
}
}
}
}).start();
tfData.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent arg0) {
//当按下回车时传递信息
try
{
DatagramPacketdp=newDatagramPacket(tfData.getText().getBytes(),
tfData.getText().getBytes().length,InetAddress.getByName(tfIP.getText()),3000);
ds.send(dp);
}
catch(Exception e)
{
e.printStackTrace();
}
tfData.setText("");
}
});
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
dispose();
System.exit(0);
}
});
}
public static void main(String[] args) {
Chat chat=new Chat("聊天工具");
chat.setVisible(true);
}
}
TCP编程
TCP网络程序的工作原理
利用UDP进行通信的两个进程是平等的,没有主从之分,它们完全可以是同一个程序的两个运行实例。而利用UDP进行通信的两个程序是不平等的,一个是服务程序(Server Socket),一个是客户程序(Client Socket)。
TCP客户端程序的与TCP服务器程序的交互过程:
(1)服务器建立一个ServerSocket类的实例,然后调用accept方法等待客户的连接。
(2)客户端程序创建一个Socket并请求与服务器建立连接。
(3)服务器接受客户的连接请求,并创建一个Socket与该客户建立连接。
(4)建立了两个连接的两个Socket在一个单独的线程(由服务器程序创建)上对话。
(5)服务器开始等待新的连接请求,当新的连接请求到达时,重复步骤(2)到步骤(5)的过程。
ServerSocket类
构造函数:
——public ServerSocket():没有与任何的端口号绑定,该ServerSocket不能直接使用,还需要调用bind方法才能完成其他构造函数所完成的功能。
——public ServerSocket(int port):指定了一个端口号,这个端口号若为0,系统就会自动为它分配一个未被使用的端口号。作为服务程序最好先指定所用的端口号。
——public ServerSocket(int port ,int backlog):第二参数用于指定在服务器很忙的时候,可以保持的等待连接的客户数量。对于第二个构造函数没有指定的情况。默认的是50.
——public ServerSocket(int port ,int backlog,InetAddress binAddr):第三个参数表示ServerSocket所绑定的IP地址,这适合计算机上有多个IP的情况。
close方法:关闭程序,释放资源。
accept方法:该方法会返回一个由户端程序创建并客户建立连接的Socket对象。在没有客户请求的情况下,该方法处于堵塞状态。
Socket类
——public Socke():不与任何服务器建立连接,不被直接使用,需要调用Socke类的connect方法,才能完成其他构造函数创建的Socket类所完成的功能。如果想要用一个Socket去轮询几个ServerSocket,可用该方法,在创建Socket对象后,再用connect方法去轮询ServerSocket服务器。
——public Socke(String host,int port)
——public Socke(InetAddress address,int port)
第二个和第三个构造函数多创建的Socket对象,会根据所指明的IP和端口号去连接服务器,第二个构造函数接受字符串格式的IP地址,第三个构造函数接受InetAddress对象格式的IP地址。
——public Socke(String host ,int port, InetAddress localAddr,int localPort)
——public Socke(InetAddress address,int port,InetAddress localAddr,int localPort)
第四个和第五个构造函数在第二个和第三个构造函数的基础上,还指明了本地的IP和程序所用的端口号,多用于本地有多个IP的时候。
当请求服务器服务后,服务器端会返回一个Socket对象与客户端的Socket建立连接。而这个服务器端的Socket对象,是不能用构造函数来创建的,而是由ServerSocket的accept方法返回的。
Socket类的getInputStream和getOutputStream方法
当服务器端Socket和客户端的Socket建立连接后,它们会以网络字节流的形式进行数据交换。
简单的服务器程序:
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".getBytes());
BufferedReader br=new BufferedReader(new InputStreamReader(ips));
System.out.println(new String(br.readLine()));
ips.close();ops.close();s.close();ss.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
一个较完善的TCP服务器模型
import java.net.*;
public class ReverseServer {
public static void main(String[] args) {
try
{
ServerSocket ss=new ServerSocket(8001);
while(true)
{
Socket s=ss.accept();
new Thread(new Servicer(s)).start();
/*要实现一个服务器与多个客户相连接,则要循环调用accept方法,并且与某个
客户进行数据交换的语句应在一个单独的进程中运行。
*否则,在服务器与某客户进行连接时,无法执行accept方法与其他的客户连接
*与某个客户进行数据交换的时候应有相应的Socket与之绑定 */
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
import java.net.*;
import java.io.*;
public class Servicer implements Runnable{
private 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));
PrintWriter pw=new PrintWriter(ops,true);
/*使用PrintWriter类的println方法可以将字符串输入到底层输出流
* 并且可以产生与具体平台相关的换行符,而PrintStream类的println
* 方法的换行符只是/n*/
while(true)
{
String strLine=br.readLine();
if(strLine.equals("quit"))
{
break;
}
String strEcho=new StringBuffer(strLine).toString();
pw.println(strLine+" --> "+strEcho);
}
br.close();
pw.close();
s.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}