zoukankan      html  css  js  c++  java
  • java网络编程socketserverTCP笔记(转)

    java网络编程socketserverTCP笔记(转)  

    2012-12-14 08:30:04|  分类: Socket |  标签:java  |举报|字号 订阅

     
     

    1 TCP的开销

    a  连接协商三次握手,c->syn->s,s->syn ack->c, c->ack->s

    b  关闭协商四次握手,c->fin->s, s->ack-c,s->fin->c,c->ack->s

    c  保持数据有序,响应确认等计算开销

    d  网络拥塞引起的重试开销

     

    2 使用知名端口初始化 serversocket可能需要超级权限。ServerSocket(int port, int backlog)参数backlog用来配置连接队列,在accept之前预先完成连接,加速连接TCP连接阶段,默认为50.

    backlog表示ServerSocket可以接受的同时最大连接数量,超过这个连接数量,将会拒绝连接。

    如果要提高吞吐量,可以通过设置更大的ServerSocket.setReceiveBufferSize来实现,但是必须在bind之前设置,也就是说要先调用无参构造,然后再调用ServerSocket.bind(SocketAddress endpoint)

    3 网络io写操作,提高吞吐量较好的实践有使用java.io.BufferedOutputStream,作为缓冲,减少用户线程和内核线程的切换频率。缓冲区大小一般大于ServerSocket.setReceiveBufferSize。

    4 避免对象流死锁,较好的实践是如果要在同一个socket上构建对象输入流和输出流,最好是先构造输出流,再构造输入流。

    5 tcp半关闭,shut down output,完成后,对方的read收到eof,结束阻塞。

    6 tcp关闭可以用socket.close,socket.getoutputstream.close,socket.getinputstream.close,较好的方式是调用socket.getoutpurtstream.close,它会把未flush的flush掉。三个方法只需调用其中一个即可。isClose方法只会告诉我们本地tcp是否关闭,但是不能告诉我们远程是否关闭。

    7 socket read 设置timeout时间,防止无止境阻塞。一般来说,timeout时间会设定为预期时间的两倍。timeout时间设置只对之后的阻塞读有效。

    8 每个socket都有send buffer和receive buffer,这个buffer在内核地址空间而非jvm。buffer的size由操作系统实现决定,一般来说是2kb。send buffer可以在tcp关闭前随时设定,通过java.net.Socket.setSendBufferSize(int)设置。但是size的设置只是一种hint,不是绝对值。size设得越大,减少网络写次数,减少拥塞控制,tcp效率、吞吐量越高,类似http://en.wikipedia.org/wiki/Nagle's_algorithm 原理。

    一般设定为MSS的三倍;至少大于对方receive buffer;receive buffer也要设定大一点,不拖send buffer后腿;

    bufferedoutputstream,bytebuffer一般也要设定为匹配的值;

    buffersize(bits)=bandwidth(bits/sec)* delay(sec),有点类似于线程数量的控制,不让cpu闲下来。这边的白话是不让buffer空下来,随时处于最大填充状态。

    9 nagle算法,为了提高网络传输效率,减少网络拥塞,延迟小包发送,组装为大包一起发送。默认为开,可以通过setTcpnodelay为true来关闭。一般来说,不会关闭,除非是需要实时交互的场景。另外如果真需要关闭,可以采用巧妙的方式,使用bufferedoutputstream,把buffer size设为大于最大请求或响应包,socket send buffer和receive buffer也设为此值,用一次操作写出请求或响应,bufferedoutputstream.flush,充分利用网络。

    10 setlinger,用于关闭socket时,进行磨蹭,拖延。

    11 keep alive,是个鸡肋。用于检测连接是否处于连接状态,检测对方是否active。它比较有争议,不是tcp协议的标准内容。另外检测需要消耗网络,当检测对方无反应,socket会被置为reset状态,不可读写。一般不推荐使用。

    可以考虑用应用层的心跳检测替代。

    参考http://hi.baidu.com/tantea/blog/item/580b9d0218f981793812bb7b.html

    12  settrafficclass,设置流量类别,只是hint作用,具体效果取决于实现。有这些类别  IPTOS_LOWCOST (0x02),IPTOS_RELIABILITY (0x04),IPTOS_THROUGHPUT (0x08),IPTOS_LOWDELAY (0x10)

    13 接口中文翻译http://hi.baidu.com/%EC%C5%BF%E1%D0%A1%B7%E5/blog/item/5d8e0f58aee147471038c29d.html

     
    14 java nio进入新时代,提供非阻塞和多路复用特性,就绪选择器,事件驱动,不再是一个线程处理一个请求,大大节约了线程数量和内存,提高了可伸缩性。
     
    15 开发广域网网络应用程序需要考虑防火墙,防火墙分为传输防火墙和应用防火墙,传输防火墙一般会拦截对非知名端口的访问,开放知名端口,如80端口;而应用防火墙一般是代理,在服务端和客户端中间,如http代理http://baike.baidu.com/view/1159398.htm
    http隧道穿透防火墙,白话为露丝想写情书给他男朋友,但是他爸妈(防火墙)不允许,于是露丝就把情书包装起来写给她的闺蜜莉莉(http 代理服务器,这个代理服务器在防火墙之内),再由莉莉转交给他男朋友。
     
    16 另外是NAT,network address translation。子网共用一个公共ip,对外界透明。http://baike.baidu.com/view/16102.htm
     
    17 UDP size比较受限(512kb),不可靠,无连接,但是成本低。丢失不重发,重发需要应用控制,要考虑发送消息是否幂等。UDP数据报是个独立传输单位,在java里UDP用java.net.DatagramPacket。适用于发送心跳场景。DatagramSocket的connect,close操作都是针对本地的,并无对连接产生什么效果,毕竟是无连接协议。
    如果想提高可靠度,可以在应用实现,clinet维护一个序列号,等待server响应这个序列号,否则进行重发策略。/*
    Java代码 
    1. * ReliableDatagramSocket.java.  
    2. * Copyright ? Esmond Pitt, 1997, 2005. All rights reserved.  
    3. * Permission to use is granted provided this copyright  
    4. * and permission notice is preserved.  
    5. */  
    6. import java.io.*;  
    7. import java.net.*;  
    8. import java.text.*;  
    9. import java.util.*;  
    10. // All times are expressed in seconds.  
    11. // ReliabilityConstants interface, just defines constants.  
    12. interface ReliabilityConstants  
    13. {  
    14. // Timeout minima/maxima  
    15. public static final int MIN_RETRANSMIT_TIMEOUT = 1;  
    16. public static final int MAX_RETRANSMIT_TIMEOUT = 64;  
    17. // Maximum retransmissions per datagram, suggest 3 or 4.  
    18. public static final int MAX_RETRANSMISSIONS = 4;  
    19. }  
    20. The  D;; class manages current and smoothed round-trip timers  
    21. and the related timeouts:  
    22. // RoundTripTimer class.  
    23. class RoundTripTimer implements ReliabilityConstants  
    24. {  
    25. float roundTripTime = 0.0f;// most recent RTT  
    26. float smoothedTripTime = 0.0f;// smoothed RTT  
    27. float deviation = 0.75f; // smoothed mean deviation  
    28. short retransmissions = 0;// retransmit count: 0, 1, 2, …  
    29. // current retransmit timeout  
    30. float currentTimeout =  
    31. minmax(calculateRetransmitTimeout());  
    32. /** @return the re-transmission timeout. */  
    33. private int calculateRetransmitTimeout()  
    34. {  
    35. return (int)(smoothedTripTime+4.0*deviation);  
    36. }  
    37. /** @return the bounded retransmission timeout. */  
    38. private float minmax(float rto)  
    39. {  
    40. return Math.min  
    41. (Math.max(rto, MIN_RETRANSMIT_TIMEOUT),  
    42. MAX_RETRANSMIT_TIMEOUT);  
    43. }  
    44. /** Called before each new packet is transmitted. */  
    45. void newPacket()  
    46. {  
    47. retransmissions = 0;  
    48. }  
    49. /** 
    50.  * @return the timeout for the packet. 
    51.  */  
    52. float currentTimeout()  
    53. {  
    54. return currentTimeout;  
    55. }  
    56. /** 
    57.  * Called straight after a successful receive. 
    58.  * Calculates the round-trip time, then updates the 
    59.  * smoothed round-trip time and the variance (deviation). 
    60.  * @param ms time in ms since starting the transmission. 
    61.  */  
    62. void stoppedAt(long ms)  
    63. {  
    64. // Calculate the round-trip time for this packet.  
    65. roundTripTime = ms/1000;  
    66. // Update our estimators of round-trip time  
    67. // and its mean deviation.  
    68. double delta = roundTripTime ? smoothedTripTime;  
    69. smoothedTripTime += delta/8.0;  
    70. deviation += (Math.abs(delta)-deviation)/4.0;  
    71. // Recalculate the current timeout.  
    72. currentTimeout = minmax(calculateRetransmitTimeout());  
    73. }  
    74. /** 
    75.  * Called after a timeout has occurred. 
    76.  * @return true if it's time to give up, 
    77.  * false if we can retransmit. 
    78.  */  
    79. boolean isTimeout()  
    80. {  
    81. currentTimeout *= 2; // next retransmit timeout  
    82. retransmissions++;  
    83. return retransmissions > MAX_RETRANSMISSIONS;  
    84. }  
    85. } // RoundTripTimer class  
    86. The D  
    87.   
    88.   
    89.   
    90. " class exports a D  method like the ones  
    91. we have already seen.  
    92. // ReliableDatagramSocket class  
    93. public class ReliableDatagramSocket  
    94. extends DatagramSocket  
    95. implements ReliabilityConstants  
    96. {  
    97. RoundTripTimer roundTripTimer = new RoundTripTimer();  
    98. private boolean reinit = false;  
    99. private long sendSequenceNo = 0; // send sequence #  
    100. private long recvSequenceNo = 0; // recv sequence #  
    101. /* anonymous initialization for all constructors */  
    102. {  
    103. init();  
    104. }  
    105. /** 
    106.  * Construct a ReliableDatagramSocket 
    107.  * @param port Local port: reeive on any interface/address 
    108.  * @exception SocketException can't create the socket 
    109.  */  
    110. public ReliableDatagramSocket(int port)  
    111. throws SocketException  
    112. {  
    113. super(port);  
    114. }  
    115. /** 
    116.  * Construct a ReliableDatagramSocket 
    117.  * @param port Local port 
    118.  * @param localAddr local interface address to use 
    119.  * @exception SocketException can't create the socket 
    120.  */  
    121. public ReliableDatagramSocket  
    122. (int port, InetAddress localAddr) throws SocketException  
    123. {  
    124. super(port, localAddr);  
    125. }  
    126. /** 
    127.  * Construct a ReliableDatagramSocket, JDK >= 1.4. 
    128.  * @param localAddr local socket address to use 
    129.  * @exception SocketException can't create the socket 
    130.  */  
    131. public ReliableDatagramSocket(SocketAddress localAddr)  
    132. throws SocketException  
    133. {  
    134. super(localAddr);  
    135. }  
    136. /** 
    137.  * Overrides DatagramSocket.connect(): 
    138.  * Does the connect, then (re-)initializes 
    139.  * the statistics for the connection. 
    140.  * @param dest Destination address 
    141.  * @param port Destination port 
    142.  */  
    143. public void connect(InetAddress dest, int port)  
    144. {  
    145. super.connect(dest, port);  
    146. init();  
    147. }  
    148. /** 
    149.  * Overrides JDK 1.4 DatagramSocket.connect(). 
    150.  * Does the connect, then (re-)initializes 
    151.  * the statistics for the connection. 
    152.  * @param dest Destination address 
    153.  */  
    154. public void connect(SocketAddress dest)  
    155. {  
    156. super.connect(dest);  
    157. init();  
    158. }  
    159. /** Initialize */  
    160. private void init()  
    161. {  
    162. this.roundTripTimer = new RoundTripTimer();  
    163. }  
    164. /** 
    165.  * Send and receive reliably, 
    166.  * retrying adaptively with exponential backoff 
    167.  * until the response is received or timeout occurs. 
    168.  * @param sendPacket outgoing request datagram 
    169.  * @param recvPacket incoming reply datagram 
    170.  * @exception IOException on any error 
    171.  * @exception InterruptedIOException on timeout 
    172.  */  
    173. public synchronized void sendReceive  
    174. (DatagramPacket sendPacket, DatagramPacket recvPacket)  
    175. throws IOException, InterruptedIOException  
    176. {  
    177. // re-initialize after timeout  
    178. if (reinit)  
    179. {  
    180. init();  
    181. reinit = false;  
    182. }  
    183. roundTripTimer.newPacket();  
    184. long start = System.currentTimeMillis();  
    185. long sequenceNumber = getSendSequenceNo();  
    186. // Loop until final timeout or some unexpected exception  
    187. for (;;)  
    188. {  
    189. // keep using the same sequenceNumber while retrying  
    190. setSendSequenceNo(sequenceNumber);  
    191. send(sendPacket);// may throw  
    192. int timeout =  
    193. (int)(roundTripTimer.currentTimeout()*1000.0+0.5);  
    194. long soTimeoutStart = System.currentTimeMillis();  
    195. try  
    196. {  
    197. for (;;)  
    198. {  
    199. // Adjust socket timeout for time already elapsed  
    200. int soTimeout = timeout?(int)  
    201. (System.currentTimeMillis()?soTimeoutStart);  
    202. setSoTimeout(soTimeout);  
    203. receive(recvPacket);  
    204. long recvSequenceNumber = getRecvSequenceNo();  
    205. if (recvSequenceNumber == sequenceNumber)  
    206. {  
    207. // Got the correct reply:  
    208. // stop timer, calculate new RTT values  
    209. long ms = System.currentTimeMillis()-start;  
    210. roundTripTimer.stoppedAt(ms);  
    211. return;  
    212. }  
    213. }  
    214. }  
    215. catch (InterruptedIOException exc)  
    216. {  
    217. // timeout: retry?  
    218. if (roundTripTimer.isTimeout())  
    219. {  
    220. reinit = true;  
    221. // rethrow InterruptedIOException to caller  
    222. throw exc;  
    223. }  
    224. // else continue   
    225. }  
    226. // may throw other SocketException or IOException  
    227. } // end re-transmit loop  
    228. } // sendReceive()  
    229. /** 
    230.  * @return the last received sequence number; 
    231.  * used by servers to obtain the reply sequenceNumber. 
    232.  */  
    233. public long getRecvSequenceNo()  
    234. {  
    235. return recvSequenceNo;  
    236. }  
    237. /** @return the last sent sequence number */  
    238. private long getSendSequenceNo()  
    239. {  
    240. return sendSequenceNo;  
    241. }  
    242. /** 
    243.  * Set the next send sequence number. 
    244.  * Used by servers to set the reply 
    245.  * sequenceNumber from the received packet: 
    246.  * 
    247. .  * socket.setSendSequenceNo(socket.getRecvSequenceNo()); 
    248.  * 
    249.  * @param sendSequenceNo Next sequence number to send. 
    250.  */  
    251. public void setSendSequenceNo(long sendSequenceNo)  
    252. {  
    253. this.sendSequenceNo = sendSequenceNo;  
    254. }  
    255. /** 
    256.  * override for DatagramSocket.receive: 
    257.  * handles the sequence number. 
    258.  * @param packet DatagramPacket 
    259.  * @exception IOException I/O error 
    260.  */  
    261. public void receive(DatagramPacket packet)  
    262. throws IOException  
    263. {  
    264. super.receive(packet);  
    265. // read sequence number and remove it from the packet  
    266. ByteArrayInputStream bais = new ByteArrayInputStream  
    267. (packet.getData(), packet.getOffset(),  
    268. packet.getLength());  
    269. DataInputStream dis = new DataInputStream(bais);  
    270. recvSequenceNo = dis.readLong();  
    271. byte[] buffer = new byte[dis.available()];  
    272. dis.read(buffer);  
    273. packet.setData(buffer,0,buffer.length);  
    274. }  
    275. /** 
    276.  * override for DatagramSocket.send: 
    277.  * handles the sequence number. 
    278.  * @param packet DatagramPacket 
    279.  * @exception IOException I/O error 
    280.  */  
    281. public void send(DatagramPacket packet)  
    282. throws IOException  
    283. {  
    284. ByteArrayOutputStreambaos = new ByteArrayOutputStream();  
    285. DataOutputStreamdos = new DataOutputStream(baos);  
    286. // Write the sequence number, then the user data.  
    287. dos.writeLong(sendSequenceNo++);  
    288. dos.write  
    289. (packet.getData(), packet.getOffset(),  
    290. packet.getLength());  
    291. dos.flush();  
    292. // Construct a new packet with this new data and send it.  
    293. byte[]data = baos.toByteArray();  
    294. packet = new DatagramPacket  
    295. (data, baos.size(), packet.getAddress(),  
    296. packet.getPort());  
    297. super.send(packet);  
    298. }  
    299. } // end of ReliableDatagramSocket class  
    Java代码  收藏代码
    1. public class ReliableEchoServer implements Runnable  
    2. {  
    3. ReliableDatagramSocket  
    4. socket;  
    5. byte[] buffer = new byte[1024];  
    6. DatagramPacket recvPacket =  
    7. new DatagramPacket(buffer, buffer.length);  
    8. ReliableEchoServer(int port) throws IOException  
    9. {  
    10. this.socket = new ReliableDatagramSocket(port);  
    11. }  
    12. public void run()  
    13. {  
    14. for (;;)  
    15. {  
    16. try  
    17. {  
    18. // Restore the receive length to the maximum  
    19. recvPacket.setLength(buffer.length);  
    20. socket.receive(recvPacket);  
    21. // Reply must have same seqno as request  
    22. long seqno = socket.getRecvSequenceNo();  
    23. socket.setSendSequenceNo(seqno);  
    24. // Echo the request back as the response  
    25. socket.send(recvPacket);  
    26. }  
    27. catch (IOException exc)  
    28. {  
    29. exc.printStackTrace();  
    30. }  
    31. } // for (;;)  
    32. } // run()  
    33. } // class  

    UDP支持多播和广播(广播是一种特殊的多播,尽量不使用广播,广播产生更多没必要的网络流量),而TCP只支持单播。一般多播用于服务发现,如jini look up。多播与多次单播相比,好处是减少开销、减小网络流量、减少服务器负载,而且速度更快,并且接受者接收到消息的时间更接近,对于某些场景来说很重要。

    多播的缺点是继承了udp,不可靠网络,依赖路由器,安全问题更加复杂。并且多播并不知道多播消息会被哪些接受者接收,也不知道接受者是否接收到,设计协议的时候需要考虑这点。

    发送多播消息,发送端可以用MulticastSocket和DatagramSocket,而接收端只能用MulticastSocket。

    多播使用场景

    (a) Software distribution

    (b) Time services

    (c) Naming services like

    (d) Stock-market tickers, race results, and the like

    (e) Database replication

    (f) Video and audio streaming: video conferencing, movie shows, etc

    (g) Multi-player gaming

    (h) Distributed resource allocation

    (i) Service discovery.

     
    18 设计server需要考虑两点:同时连接的客户数量,每个连接的持续时间。当客户超过一个的时候,我们就要考虑用多线程,这个时候就涉及到线程如何创建、线程运行、线程销毁。服务器端由等待连接的线程和处理连接的线程组成。
    服务器模型进化趋势:单线程接收连接、处理连接,无法同时处理多个客户,淘汰;每接收一个请求,创建一个线程对请求处理,可以并发,但是会耗尽服务器资源;采用线程池方式,并进行阀值控制,保护服务器,并进行优雅降级。
    关于线程池线程数量的控制,一般是预创建N个线程,当峰值访问来临时,临时创建M个动态线程,一旦访问峰值降下来,再释放动态线程。
    连接模型可以分为一个连接一个对话(请求-响应);一个连接多次对话。不同的模型,连接释放的方式不一样。
    代码如下
    Java代码 
    1. public void processSession(Socket socket)  
    2. {  
    3. receive(request);  
    4. // process request and construct reply, not shown …  
    5. send(reply);  
    6. // close connection  
    7. socket.close();// exception handling not shown  
    8. }  
     
    Java代码 
    1. void processSession(Socket socket)  
    2. {  
    3. while (receive(request)) // i.e. while not end-of-stream  
    4. {  
    5. // process request and construct reply, not shown …  
    6. send(reply);  
    7. }  
    8. // close connection  
    9. socket.close();// exception handling not shown  
    10. }  
     多次对话的连接释放方式,可以根据输入流的返回结果,或者遇到eof来关闭连接。
    归结点
    (a) On receipt of an end-of-stream when reading the connection.
    (b) If the request or the client is deemed invalid.
    (c) On detection of a read timeout or idle timeout on the connection.
    (d) After writing a reply
     
    19 设计客户端,一般需要考虑连接失败和读数据超时。为了减少创建连接的开销,一般还会使用线程池,如rmi。
    在请求-响应事务中,一般会采取header_body_trailler的结构。结合使用gathering、scattering io来较少内存和cpu开销
    Java代码 
    1. // Initialization - common to both ends  
    2. static final int HEADER_LENGTH = 16;  
    3. static final int BODY_LENGTH = 480;  
    4. static final int TRAILER_LENGTH = 16;  
    5. ByteBuffer header = ByteBuffer.allocate(HEADER_LENGTH);  
    6. ByteBuffer body = ByteBuffer.allocate(BODY_LENGTH);  
    7. ByteBuffer trailer = ByteBuffer.allocate(TRAILER_LENGTH);  
    8. ByteBuffer[]  
    9. buffers = new ByteBuffer[]  
    10. { header, body, trailer };  
    11. // sending end - populate the buffers, not shown  
    12. long count  = channel.write(buffers);  
    13. // repeat until all data sent  
    14. // receiving end  
    15. long count = channel.read(buffers);  
    16. // repeat until all data read  
     
    对于浏览器加载页面的过程,由于加载对交互顺序不敏感,所以client可以同时并发多个连接、多个线程并行从服务端获取数据
     
    20 jdk为编写并发服务器提供了很好的支持。如Executors提供了线程池,java.util.concurrent.ThreadPoolExecutor.DiscardPolicy提供了阀值控制,ThreadFactory提供了创建线程的方式。
     
    21 客户端技术一般来用连接池,如memcache client每个连接某时刻只在一个request-reply事务中。或者多个事务公用一个连接,比如tair client,需要在协议上维护request-reply的匹配关系。
     
    22 网络编程的八个谬论
    a 网络是可靠的
    b 网络没有延迟
    c 带宽是无限的
    d 网络是安全的
    e 网络拓扑不会变
    f  只有一个管理员
    g 传输开销为0
    h  网络是均匀的,网络由不同带宽的节点组成,木桶理论,以最小的那个为带宽。
    i 网络io如同磁盘io。网络io更容易出错,不如磁盘稳定
    j 和peer的状态是同步的。除非在应用层接收到ack,否则不要假定对方收到你的数据。
    k 所有的网络失败都是可以检测的。
    l  资源是无限的。其实网络编程涉及的资源包括端口、缓冲都是有限的
    m 应用可以无限等待远程服务。任何远程调用都应该设定超时时间。
    n 远程服务的响应是及时的
    o 有单点失败。在分布式系统中,一般一个host的失败不会引发整个系统的崩溃。除非有一个中心节点。
    p 只有一个资源分配器。每个host的资源都可以独立分配。
    q 时间是完全统一的
     
     
    参考:
  • 相关阅读:
    怎么与用户有效的沟通以获取用户的真实需求?
    面向过程(或者叫结构化)分析方法与面向对象分析方法到底区别在哪里?
    当下大部分互联网创业公司为什么都愿意采用增量模型来做开发?
    【第八周】回到起点,从头再来
    【第七周作业】项目开发心得
    【第六周作业】项目代码的编写规范
    【第五周作业】寸步难行
    【第四周作业】参加项目开发之后的一些体会
    【第三周作业】对于软件工程学的一些理解
    【第二周作业】面向过程(或者叫结构化)分析方法与面向对象分析方法到底区别在哪里?
  • 原文地址:https://www.cnblogs.com/wzhanke/p/4817719.html
Copyright © 2011-2022 走看看