聶永的博客

          記錄工作/學習的點點滴滴。

          100萬并發連接服務器筆記之處理端口數量受限問題

          第二個遇到的問題:端口數量受限

          一般來說,單獨對外提供請求的服務不用考慮端口數量問題,監聽某一個端口即可。但是向提供代理服務器,就不得不考慮端口數量受限問題了。當前的1M并發連接測試,也需要在客戶端突破6萬可用端口的限制。

          單機端口上限為65536

          端口為16進制,那么2的16次方值為65536,在linux系統里面,1024以下端口都是超級管理員用戶(如root)才可以使用,普通用戶只能使用大于1024的端口值。
          系統提供了默認的端口范圍:

          cat /proc/sys/net/ipv4/ip_local_port_range
          32768 61000

          大概也就是共61000-32768=28232個端口可以使用,單個IP對外只能發送28232個TCP請求。
          以管理員身份,把端口的范圍區間增到最大:

          echo "1024 65535"> /proc/sys/net/ipv4/ip_local_port_range

          現在有64511個端口可用.
          以上做法只是臨時,系統下次重啟,會還原。 更為穩妥的做法是修改/etc/sysctl.conf文件,增加一行內容

          net.ipv4.ip_local_port_range= 1024 65535

          保存,然后使之生效:

          sysctl -p

          現在可以使用的端口達到64510個(假設系統所有運行的服務器是沒有占用大于1024的端口的,較為純凈的centos系統可以做到),要想達到50萬請求,還得再想辦法。

          增加IP地址

          一般假設本機網卡名稱為 eth0,那么手動再添加幾個虛擬的IP:

          ifconfig eth0:1 192.168.190.151
          ifconfig eth0:2 192.168.190.152 ......

          或者偷懶一些:

          for i in `seq 1 9`; do ifconfig eth0:$i 192.168.190.15$i up ; done

          這些虛擬的IP地址,一旦重啟,或者 service network restart 就會丟失。

          為了模擬較為真實環境,在測試端,手動再次添加9個vmware虛擬機網卡,每一個網卡固定一個IP地址,這樣省去每次重啟都要重新設置的麻煩。

          192.168.190.134
          192.168.190.143
          192.168.190.144
          192.168.190.145
          192.168.190.146
          192.168.190.147
          192.168.190.148
          192.168.190.149
          192.168.190.150
          192.168.190.151

          在server服務器端,手動添加橋接網卡和NAT方式網卡

          192.168.190.230
          192.168.190.240
          10.95.20.250
          

          要求測試端和服務器端彼此雙方都是可以ping通。

          網絡四元組/網絡五元組

          四元組是指的是

          {源IP地址,源端口,目的IP地址,目的端口}

          五元組指的是(多了協議)

          {源IP地址,目的IP地址,協議號,源端口,目的端口}

          在《UNIX網絡編程卷1:套接字聯網API(第3版)》一書中,是這樣解釋:
          一個TCP連接的套接字對(socket pari)是一個定義該連接的兩個端點的四元組,即本地IP地址、本地TCP端口號、外地IP地址、外地TCP端口號。套接字對唯一標識一個網絡上的每個TCP連接。

          ......

          標識每個端點的兩個值(IP地址和端口號)通常稱為一個套接字。

          以下以四元組為準。在測試端四元組可以這樣認為:

          {本機IP地址,本機端口,目的IP地址,目的端口}

          請求的IP地址和目的端口基本上是固定的,不會變化,那么只能從本機IP地址和本機端口上考慮,端口的范圍一旦指定了,那么增加IP地址,可以增加對外發出的請求數量。假設系統可以使用的端口范圍已經如上所設,那么可以使用的大致端口為64000個,系統添加了10個IP地址,那么可以對外發出的數量為 64000 * 10 = 640000,數量很可觀。

          只有{源IP地址,源端口}確定對外TCP請求數量

          經測試,四元組里面,只有{源IP地址,源端口}才能夠確定對外發出請求的數量,跟{目的IP地址,目的端口}無關。

          測試環境

          在server端,并且啟動./server兩次,分別綁定8000端口和9000端口

          ./server -p 8000
          ./server -p 9000
          

          本機IP、端口綁定測試程序

          這里寫一個簡單的測試綁定本機IP地址和指定端口的客戶端測試程序。

          可以看到libevent-*/include/event2/http.h內置了對綁定本地IP地址的支持:

          /** sets the ip address from which http connections are made */
          void evhttp_connection_set_local_address(struct evhttp_connection *evcon,
          const char *address);
          

          不用擔心端口,系統自動自動隨機挑選,除非需要特別指定:

          /** sets the local port from which http connections are made */
          void evhttp_connection_set_local_port(struct evhttp_connection *evcon,
          ev_uint16_t port);
          

          編譯

          gcc -o client3 client3.c -levent
          

          client3運行參數為

          • -h 遠程主機IP地址
          • -p 遠程主機端口
          • -c 本機指定的IP地址(必須可用)
          • -o 本機指定的端口(必須可用)

          測試用例,本機指定同樣的IP地址和端口,但遠程主機和IP不一樣.
          在一個測試端打開一個終端窗口1,切換到 client3對應位置

          ./client3 -h 192.168.190.230 -p 8000 -c 192.168.190.148 -o 4000
          

          輸出為

          remote host is 192.168.190.230
          remote port is 8000
          local ip is 192.168.190.148
          local port is 4000
          >Chunks: 1 Bytes: 505
          

          再打開一個測試端終端窗口2,執行:

          ./client3 -h 192.168.190.240 -p 9000 -c 192.168.190.148 -o 4000
          

          窗口2程序,無法執行,自動退出。
          接著在窗口2終端繼續輸入:

          ./client3 -h 192.168.190.230 -p 8000 -c 192.168.190.148 -o 4001
          

          注意,和窗口1相比,僅僅改變了端口號為4001。但執行結果,和端口1輸出一模一樣,在等待接收數據,沒有自動退出。

          剩下的,無論怎么組合,怎么折騰,只要一對{本機IP,本機端口}被占用,也就意味著對應一個具體的文件句柄,那么其它程序將不能夠再次使用。

          Java怎么綁定本地IP地址?

          java綁定就很簡單,但有些限制,不夠靈活,單純從源碼中看不出來,api doc可以告訴我們一些事情。 打開JDKAPI1_6zhCN.CHM,查看InetSocketAddress類的構造函數說明:

          public InetSocketAddress(InetAddress addr, int port)
          根據 IP 地址和端口號創建套接字地址。 有效端口值介于 0 和 65535 之間。端口號 zero 允許系統在 bind 操作中挑選暫時的端口。

          null 地址將分配通配符 地址。

          參數:
          addr - IP 地址
          port - 端口號
          拋出:
          IllegalArgumentException - 如果 port 參數超出有效端口值的指定范圍。


          public InetSocketAddress(String hostname, int port)
          根據主機名和端口號創建套接字地址。
          嘗試將主機名解析為 InetAddress。如果嘗試失敗,則將地址標記為未解析。

          如果存在安全管理器,則將主機名用作參數調用其 checkConnect 方法,以檢查解析它的權限。這可能會導致 SecurityException 異常。

          有效端口值介于 0 和 65535 之間。端口號 zero 允許系統在 bind 操作中挑選暫時的端口。

          參數: hostname - 主機名
          port - 端口號
          拋出:
          IllegalArgumentException - 如果 port 參數超出有效端口值的范圍,或者主機名參數為 null。
          SecurityException - 如果存在安全管理器,但拒絕解析主機名的權限。
          另請參見:
          isUnresolved()

          InetSocketAddress的兩個構造函數都支持,看情況使用。注意int port傳遞值為0,即可做到系統隨機挑選端口。追蹤一下源代碼,發現最終調用

          private native void socketBind(InetAddress address, int port) throws IOException;
          

          如何查看socketBind的原始C代碼,我就不清楚了,您若知曉,希望指教一下。 構造一個InetSocketAddress對象:

          SocketAddress localSocketAddr = new InetSocketAddress("192.168.190.143", 0);
          

          然后傳遞給需要位置即可。諸如使用netty連接到某個服務器上,在connect時指定遠方地址,以及本機地址

          ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) Attempts a new connection with the specified remoteAddress and the specified localAddress.

          Netty 客戶端連接API見:

          Linux支持綁定本機IP、端口原理

          說是原理,有些牽強,因為linux C提供了如何綁定函數,框架或者高級語言再怎么封裝,在linux平臺下面,需要這么調用:

          struct sockaddr_in clnt_addr;
          ....
          clnt_addr.sin_family = AF_INET;
          clnt_addr.sin_addr.s_addr = INADDR_ANY; //綁定本機IP地址
          clnt_addr.sin_port = htons(33333); //綁定本機端口
          if (bind(sockfd, (struct sockaddr *) &clnt_addr,
          sizeof(clnt_addr)) < 0) error("ERROR on binding");
          if (connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0) error("ERROR connecting");
          .......
          

          構造一個clnt_addr結構體,本地IP或者端口賦值,在connect之前,先bind,就這么簡單。

          更完整例子,可以參考 http://stackoverflow.com/questions/4852256/need-a-complete-snippet-example-of-binding-tcp-client-socket

          有關端口的更詳細解釋,請參考《UNIX網絡編程卷1:套接字聯網API(第3版)》2.9節 端口號部分。

           

          有關端口的問題,到此為止,下一篇,回到測試。

          posted on 2013-04-09 17:26 nieyong 閱讀(18415) 評論(0)  編輯  收藏 所屬分類: C1M

          公告

          所有文章皆為原創,若轉載請標明出處,謝謝~

          新浪微博,歡迎關注:

          導航

          <2013年4月>
          31123456
          78910111213
          14151617181920
          21222324252627
          2829301234
          567891011

          統計

          常用鏈接

          留言簿(58)

          隨筆分類(130)

          隨筆檔案(151)

          個人收藏

          最新隨筆

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 海林市| 沁源县| 宿松县| 麻阳| 遂溪县| 新昌县| 涟源市| 陵水| 泗洪县| 桐柏县| 工布江达县| 雷州市| 青川县| 灵璧县| 郎溪县| 荆州市| 湘阴县| 三明市| 岢岚县| 出国| 留坝县| 望城县| 华坪县| 延津县| 乌兰察布市| 黄梅县| 抚州市| 湾仔区| 巴东县| 综艺| 开阳县| 廊坊市| 资中县| 焉耆| 邓州市| 正蓝旗| 呼伦贝尔市| 南郑县| 阳高县| 黄浦区| 修武县|