一、網絡基礎1
網絡的劃分
- 局域網(覆蓋範圍在1000m內)
- 城域網(覆蓋範圍在20㎞內)
- 廣域網(覆蓋範圍大於20km),(互聯網 / 因特網是更大的國際性的廣域網- - - 容災性更強,以太網 / 令牌環網 是組網方式)
IP地址
uint32_t - - - 無符號4個字節的整數
1、在網絡中作爲主機的唯一標識,網絡中主機之間的定位(哪個主機與哪個主機之間進行通信),通過IP地址進行標識。
2、網絡中每條數據中都會包含:源端的IP地址 / 對端的IP地址;
3、ipv4 : uint32_t - - -DHCP/NAT
ipv6 : uint8_t addr[16] - - - 推廣度還很低
端口號
uint16_t - - -無符號2個字節的整數
在一臺主機上唯一標識一個進程 - - - - 編寫通信程序的時候,必須告訴計算機,發往哪個端口的數據應該交給我處理;
一個端口只能被一個進程佔用,然而一個進程可以使用多個端口
在網絡通信的每條數據中,都會包含有 源端端口 / 對端端口 - - - 標識了這個數據從哪個進程發送出來,要交給哪個進程處理。
網絡通信協議
網絡通信證的數據格式約定,遵循統一通信協議標準,才能實現實質通信,實現網絡互聯
協議分層
根據通信場景的不同,提供的服務不同,使用的協議不同進行的層次劃分
典型協議分層
OSI七層參考模型:應用層、表示層、會話層、傳輸層、網絡層、鏈路層、物理層。
TCP/IP五層模型:應用層、傳輸層、網絡層、鏈路層、物理層。
- 應用層:負責應用程序之間如何溝通;HTTP / FTP / DNS / DHCP…
- 傳輸層:負責進程之間的數據傳輸; TCP / UDP
- 網絡層:負責地址管理與路由選擇; IP / 路由器
- 鏈路層:負責相鄰設備之間的數據傳輸; 以太網協議 / 交換機
- 物理層:負責物理光電信號的傳輸; 以太網協議 / 集線器
網絡通信數據的封裝與分用流程
主機字節序:一個主機字節序的大小端取決於cpu架構 - - - X86 / MIPS
int a =0x 01 02 03 04 -> 高位 000000001 00000010 00000011 00000100 低位
uchar *b = (uchar *)&a 內存低地址 b[0] b[1] b[2] b[3] 內存高地址
大端字節序:低地址存高位 b[0]=01、 b[1]=02、 b[2]=03、 b[3]=04
小端字節序:低地址存低位 b[0] =04、 b[1]=03、 b[2]=02、 b[3]=01
網絡字節序:網絡通信中的字節序標準(將自己的數據字節序轉換成標準字節序再進行傳輸) - - - 避免因爲主機字節序不同造成是數據二義。
- 字節序:cpu對內存中數據存儲是順序;
- 主機字節序的分類:大端字節序、小端字節序;
主機字節序跟網絡通信的關係:不同主機字節序的主機進行通信容易造成數據二義性。
網絡通信中,存儲單元大於一個字節的數據類型需要進行網絡字節序的轉換。
判斷一個主機的字節序:
union{int a; char b;} tmp_t --- 聯合體成員共用同一份空間;
tmp_t tmp; tmp.a=1 if(tmp.b==1){小端}
網絡通信程序編寫的時候,到底在傳輸層用 tcp 協議好還是 udp 協議好?
tcp:傳輸控制協議,面向連接,可靠傳輸,面向字節流。(tcp 保證可靠傳輸,但是傳輸速度沒有 udp 快)。
udp:用戶數據協議,無連接,不可靠,面向數據報。(tcp應用於安全性要求高的場景/udp應用於實時性要求高的場景)。
二、udp套接字編程
網絡通信中的數據,必須包含:源端IP、源端端口、對端IP、對端端口、協議。
udp通信編程
客戶端不主動綁定地址端口,是爲了降低端口衝突的概率。
udp套接字(socket)接口介紹
1、創建套接字
int socket(int domain, int type, int protocol);
domain:地址域,確定本次socket通信使用哪種協議版本的地址結果,不同協議版本有不同的地址結構。AF_INT IPV4網絡協議。
type:套接字類型(流式套接字- - -SOCK_STREAM / 數據報套接字- - -SOCK_DGRAM)
protocol:協議類型(通常就是傳輸層協議的選擇IPPROTO_TCP / IPPROTO_UDP),默認爲0,流式默認tcp / 數據報默認udp。
返回值:文件描述符 - - -非負整數- - -套接字所有其它接口的操作句柄;失敗返回 -1;
2、爲套接字綁定地址信息
int bind(int sockfd, struct sockaddr *addr, socklen_t len);
sockfd:創建套接字返回的操作句柄;
addr:要綁定的地址信息;
len:要綁定的地址信息長度;
bind 可以綁定不同的地址結構,爲了實現接口統一,因此用戶定義地址結構的時候,定義自己需要的地址結構(例如:ipv4就使用struct sockaddr_in),但是進行綁定的時候,統一類型強轉成爲sockaddr* 類型。
bind(fd, struct sockaddr *addr, len);
{
if(addr->sa_family == AF_inet)
{
//綁定IPV4地址信息,這個結構體按照sockaddr_in進行解析
}
else if(addr->sa_family == AF_INET6)
{//IPV6地址綁定,按照IPV6地址結構解析}
else if(addr->sa_family == AF LOCAL)
{}
}
3、接收數據
不僅僅接收數據,還要通過接收得知這個數是誰發的,以便於進行回覆。
ssize_t recvfrom(int sockfd, char *buf, int len, int flag, struct sockaddr *peer_addr, socklen_t *addrlen);
sockfd::socket操作句柄;
buf:一塊緩衝區,用於接收從接收緩衝區中取出的數據;
len:想要接收的數據長度;
flag:操作選項標誌,默認爲0,表示阻塞操作;
peer_addr:發送方的地址信息;
addrlen:想要獲取的地址信息長度以及返回實際長度;
返回值:成功返回實際接收到的數據字節長度;失敗返回-1;
4、發送數據
ssize_t sendto(int sockfd, char *data, int len, int flag, struct sockaddr *peer_addr, socklen_t addrlen);
sockfd:socket操作句柄;
data:要發送的數據首地址;
len:要發送數據長度;
flag:默認爲0,表示阻塞操作;
peer_addr:接收方的地址信息;
addrlen:地址信息長度;
返回值:成功返回實際發送的數據的字節長度,失敗返回-1;
5、關閉套接字
int socket(int domain, int type, int protocol);
使用c++封裝一個 UdpSocket 類,實例化的每一個對象都是udp 套接字,並且能夠通過成員接口實現Udp通信流程。
class UspSocket
{
public:
bool Socket(); //創建套接字
bool Bind(const std::string &ip, uint16_t prot); //爲套接字綁定地址信息
bool Recv(std::string *buf, std::string *ip, uint16_t *port); //接收數據,獲取發送端地址信息
bool Send(const std::string &data, onst std::string &ip, const uint16_t port); //發送數據
bool Close(); //關閉套接字
private:
int _sockfd;
}
網絡字節序的轉換接口
uint32_t htonl(uint32_t hostlong); //hton ---主機字節序到網絡字節序的轉換
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong); //ntoh --- 網絡字節序到主機字節序的轉換
uint16_t ntohs(uint16_t netshort);
in_addr_t inet_addr(const char *cp); //將字符串的點分十進制IP地址轉換成網絡字節序的整數IP地址
char *inet_ntoa(struct in_addr in); //將網絡字節序的整數IP地址轉換成字符串點分十進制IP地址
const char *inet_ntop(int af,const void *src, char *dst, socklen_t size); //將網絡字節序的整數IP地址,轉換成字符串的IP地址(兼容IPV6)
int inet_pton(int af, const char *src, void *dst); //將字符串的IP地址轉換成網絡字節序的整數IP地址
udpsocket服務端(C語言)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>//地址結構體定義的頭文件
#include <arpa/inet.h>//字節序轉換接口的頭文件
#include <sys/socket.h>//套接字接口的頭文件
int main()
{
uint16_t port = 9000;
char *ip = "172.31.43.144";
//創建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd < 0) {
perror("socket error");
return -1;
}
//綁定地址信息: 1.定義地址結構 / 2. 地址信息賦值 / 3. 進行綁定
struct sockaddr_in addr;
addr.sin_family = AF_INET;//賦值地址域類型
addr.sin_port = htons(port);//賦值端口
addr.sin_addr.s_addr = inet_addr(ip);//賦值IP地址
socklen_t len = sizeof(struct sockaddr_in);
int ret = bind(sockfd, (struct sockaddr*)&addr, len);
if (ret < 0) {
perror("bind error");
return -1;
}
//接收數據: 不但要接收數據,還要接收發送方地址信息
char tmp[4096] = {0};
struct sockaddr_in cli_addr;
char cli_ip[24] = {0};
uint16_t cli_port = 0;
ret = recvfrom(sockfd, tmp, 4096, 0, (struct sockaddr*)&cli_addr, &len);
if (ret < 0) {
perror("recvfrom error");
return -1;
}
strcpy(cli_ip, inet_ntoa(cli_addr.sin_addr));
cli_port = ntohs(cli_addr.sin_port);
//發送數據: 將接收到的數據在回送給客戶端
ret = sendto(sockfd, tmp, ret, 0, (struct sockaddr*)&cli_addr, len);
if (ret < 0) {
perror("sendto error");
return -1;
}
//關閉套接字
close(sockfd);
return 0;
}
udpsocket服務端(C++語言)
#include <cstdio>
#include <string>
#include <netinet/in.h>//包含地址結構信息
#include <arpa/inet.h>//字節序轉換接口
#include <sys/socket.h>//套接字接口信息
class UdpSocket {
public:
UdpSocket():_sockfd(-1){}
bool Socket() {//創建套接字
//socket(地址域, 套接字類型, 協議類型)
_sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (_sockfd < 0) {
perror("socket error");
return false;
}
return true;
}
// 爲套接字綁定地址信息
bool Bind(const std::string &ip, uint16_t port) {
//定義IPV4地址結構 struct sockaddr_in
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);//htons將主機字節序短整型數據轉換爲網絡字節序數據
addr.sin_addr.s_addr = inet_addr(ip.c_str());//將字符串IP地址轉換爲網絡字節序
//bind(描述符, 地址信息, 地址信息長度)
socklen_t len = sizeof(struct sockaddr_in);
int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
if (ret < 0) {
perror("bind error");
return false;
}
return true;
}
//接收數據,獲取發送端地址信息
bool Recv(std::string *buf, std::string *ip=NULL, uint16_t *port=NULL) {
//recvfrom(套接字句柄,接收緩衝區,數據長度,標誌, 源端地址,地址長度)
struct sockaddr_in peer_addr;
socklen_t len = sizeof(struct sockaddr_in);
char tmp[4096] = {0};
int ret = recvfrom(_sockfd, tmp, 4096, 0, (struct sockaddr*)&peer_addr, &len);
if (ret < 0) {
perror("recvfrom error");
return false;
}
buf->assign(tmp, ret); // assign從指定字符串中截取指定長度的數據到buf中
if (port != NULL) {
*port = ntohs(peer_addr.sin_port);//網絡字節序到主機字節序的轉換
}
if (ip != NULL) {
*ip = inet_ntoa(peer_addr.sin_addr);//網絡字節序到字符串IP地址的轉換
}
return true;
}
// 發送數據
bool Send(const std::string &data, const std::string &ip, const uint16_t port) {
//sendto(套接字句柄,數據首地址,數據長度,標誌,對端地址信息,地址信息長度)
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
socklen_t len = sizeof(struct sockaddr_in);
int ret = sendto(_sockfd, data.c_str(), data.size(), 0,
(struct sockaddr*)&addr, len);
if (ret < 0) {
perror("sendto error");
return false;
}
return true;
}
bool Close(){
if (_sockfd > 0) {
close(_sockfd);
_sockfd = -1;
}
return true;
}// 關閉套接字
private:
int _sockfd;
};
udpsocket客戶端(C語言)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
int main()
{
//1.創建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd < 0) {
perror("socket error");
return -1;
}
//2.綁定地址信息(不推薦)
//3.發送數據: 要給誰發送什麼數據--發送的對端地址一定是服務端綁定的地址
struct sockaddr_in srv_addr;
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(9000);
srv_addr.sin_addr.s_addr = inet_addr("172.31.43.144");
socklen_t len = sizeof(struct sockaddr_in);
char tmp[1024] = {0};
fgets(tmp, 1024, stdin);
sendto(sockfd, tmp, strlen(tmp), 0, (struct sockaddr*)&srv_addr, len);
//4.接收數據
char buf[1024] = {0};
//對於客戶端來說,本身久知道服務端地址,因此其實根本不用接收服務端地址信息
recvfrom(sockfd, buf, 1023, 0, (struct sockaddr*)&srv_addr, &len);
printf("server say:%s\n", buf);
//5.關閉套接字
close(sockfd);
return 0;
}
udpsocket客戶端(C++語言)
#include <iostream>
#include <string>
#include "udpsocket.hpp"
#define CHECK_RET(q) if((q)==false){return -1;}
int main (int argc, char *argv[])
{
//客戶端參數獲取的IP地址是服務端綁定的地址,也就是客戶端發送數據的目標地址
//不是爲了自己綁定的
if (argc != 3) {
std::cout << "Usage: ./udp_cli ip port\n";
return -1;
}
std::string srv_ip = argv[1];
uint16_t srv_port = std::stoi(argv[2]);
UdpSocket cli_sock;
//創建套接字
CHECK_RET(cli_sock.Socket());
//綁定地址(不推薦)
while(1) {
//發送數據
std::cout << "client say:";
std::string buf;
std::cin >> buf;
if (buf == "quit") {
break;
}
CHECK_RET(cli_sock.Send(buf, srv_ip, srv_port));
//接收數據
buf.clear();
CHECK_RET(cli_sock.Recv(&buf));//默認參數可以不用賦予
std::cout << "server say: " << buf << std::endl;
}
//關閉套接字
cli_sock.Close();
//...
return 0;
}
三、tcp套接字編程
面向連接,可靠傳輸,面向字節流。
面向連接:必須建立了連接保證雙方都具有數據收發的能力,才能開始通信;(udp是隻需要知道對端地址就可以直接發送數據)。
tcp套接字(socket)接口介紹
1、創建套接字
int socket(int domain, int type, int protocol);
type:SOCK_DGRAM - - - 數據報套接字 / SOCK_STREAM - - -流式套接字
protocol:IPPROTO_TCP
2、綁定地址信息
int bind(int sockfd, struct sockaddr *addr, socklen_t len);
3、開始監聽
listen(int sockfd, int backlog);
sockfd:將哪個套接字設置爲監聽狀態,並且監聽狀態後可以開始接收客戶端連接請求。
backlog:同一時間的併發連接數,決定同一時間最多接收多少個客戶端的連接請求。
4、獲取新建連接
從已完成連接隊列中取出一個 socket,並且返回這個socket的描述符操作句柄。
int accept(int sockfd, struct sockaddr *cli_addr, socklen_t *len);
sockfd:監聽套接字,表示要獲取哪個 tcp 服務端套接字的新建連接;
cli——addr:這個新的套接字對應的客戶端地址信息
len:地址信息長度;
返回值:新建socket 套接字的描述符 - - - 外部程序中的操作句柄;
5、接受數據 / 發送數據
因爲 tcp 通信套接字中已經標識了五元組,因此不需要接收數據的時候獲取對方地址信息,發送數據的時候也不需要指定對方的地址信息。
ssize_t recv(int sockfd, char *buf, int len, int flag);
默認阻塞,沒有數據則等待,連接斷開返回0,不再阻塞
ssize_t send(int sockfd, char *data, int len, int flag);
默認阻塞,緩衝區數據滿了則等待,連接斷開則觸發SIGPIPE異常
6、關閉套接字
int close(fd);
7、向服務端發送連接請求
int connect(int sockfd, struct sockaddr *srv_addr, int len);
srv_addr:服務端地址信息 - - - 給誰發送連接請求
connect這個接口也會在sockfd 的套接字socket 中描述對端地址信息
封裝一個TcpSocket類
每一個實例化的對象都是一個socket 通信連接,通過這個對象的成員完成通信流程
class TcpSocket{
public:
TcpSocket();
bool Socket();
bool Bind(const std::string &ip, uint16_t port);
bool Listen(int backlog = MAX_LISTEN);
bool Accept(TcpSocket *new_sock, std::string *ip = NULL, uint16_t *port = NULL);
bool Recv(std::string *buf);
bool Send(const std::string &data);
bool Close();
bool Connect(const std::string &ip, uint16_t port);
private:
int _sockfd;
}
while(1)
{
-
1、獲取新連接;
一旦獲取到一個新連接,就啓動一個新的執行流(多線程/多進程),讓這個新的執行流去與客戶端進行通信
a、因爲沒有新連接到來的阻塞,不會影響與客戶端的通信
b、與客戶端通信的阻塞不會影響獲取新連接 -
2、通過新連接接收指定客戶端數據;
-
3、通過新連接向指定客戶端發送數據;
}
當前,在一個執行流中,完成好多個操作,獲取新連接、接收數據局、發送數據,然而這幾個操作都有可能導致流程阻塞,因爲我們在固定流程下,有可能會對沒有數據到來的socket進行操作,因此導致阻塞。
tcppsocket(C++頭文件hpp)
#include <cstdio>
#include <string>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
//listen的第二個參數決定同一時間能夠接收多少客戶端連接
//並不決定整體通信能夠接收多少客戶端連接
#define MAX_LISTEN 5
#define CHECK_RET(q) if((q)==false){return -1;}
class TcpSocket {
public:
TcpSocket ():_sockfd(-1){}
bool Socket() {
//socket(地址域, 套接字類型, 協議類型)
_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (_sockfd < 0) {
perror("socket error");
return false;
}
return true;
}
bool Bind(const std::string &ip, uint16_t port) {
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
socklen_t len = sizeof(struct sockaddr_in);
int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
if (ret < 0) {
perror("bind error");
return false;
}
return true;
}
bool Listen(int backlog = MAX_LISTEN) {
//listen(套接字描述符, 最大併發連接數)
int ret = listen(_sockfd, backlog);
if (ret < 0) {
perror("listen error");
return false;
}
return true;
}
bool Accept(TcpSocket *new_sock, std::string *ip=NULL, uint16_t *port=NULL) {
//新建套接字描述符 = accept(監聽套接字描述符, 客戶端地址信息,地址長度);
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
int new_fd = accept(_sockfd, (struct sockaddr*)&addr, &len);
if (new_fd < 0) {
perror("accept error");
return false;
}
new_sock->_sockfd = new_fd;
if (ip != NULL) {
(*ip) = inet_ntoa(addr.sin_addr);
}
if (port != NULL) {
*port = ntohs(addr.sin_port);
}
return true;
}
bool Recv(std::string *buf) {
//recv(通信套接字描述符,緩衝區首地址,接收數據長度, 標誌位-0阻塞)
char tmp[4096] = {0};
int ret = recv(_sockfd, tmp, 4096, 0);
if (ret < 0) {
perror("recv error");
return false;
}else if (ret == 0) {//recv默認阻塞,沒有數據就會等待,返回0,表示連接斷開
printf("connection broken\n");
return false;
}
buf->assign(tmp, ret);
return true;
}
bool Send(const std::string &data) {
//send(描述符,要發送數據首地址,要發送的數據長度,標誌位-0阻塞)
int ret = send(_sockfd, data.c_str(), data.size(), 0);
if (ret < 0) {
perror("send error");
return false;
}
return true;
}
bool Close() {
if (_sockfd > 0) {
close(_sockfd);
_sockfd = -1;
}
return true;
}
bool Connect(const std::string &ip, uint16_t port) {
//向服務端發起連接
//connect(描述符, 服務端地址信息, 地址信息長度)
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
socklen_t len = sizeof(struct sockaddr_in);
int ret = connect(_sockfd, (struct sockaddr *)&addr, len);
if (ret < 0) {
perror("connect error");
return false;
}
return true;
}
private:
int _sockfd;
};
tcppsocket服務端(C++語言)
/*1. 創建套接字
2. 綁定地址信息
3. 開始監聽
4. 獲取新連接
5. 收發數據
6. 關閉套接字
*/
#include <iostream>
#include "tcpsocket.hpp"
int main (int argc, char *argv[])
{
if (argc != 3) {
std::cout << "Usage: ./tcp_srv ip port\n";
return -1;
}
std::string ip = argv[1];
uint16_t port = std::stoi(argv[2]);
TcpSocket lst_sock;
CHECK_RET(lst_sock.Socket());//創建監聽套接字
CHECK_RET(lst_sock.Bind(ip, port));//爲監聽套接字綁定地址
CHECK_RET(lst_sock.Listen());//開始監聽
while(1) {
TcpSocket new_sock;
bool ret = lst_sock.Accept(&new_sock);//通過監聽套接字獲取新建連接
if (ret == false) {
continue;//服務端不能因爲或一個新建套接字失敗就退出
}
std::string buf;
new_sock.Recv(&buf);//通過新建連接與指定客戶端進行通信
std::cout << "client say: " << buf << std::endl;
buf.clear();
std::cout << "server say: ";
std::cin >> buf;
new_sock.Send(buf);
}
lst_sock.Close();
return 0;
}
tcppsocket客戶端(C++語言)
/*
實現tcp客戶端流程
1. 創建套接字
2. 綁定地址信息(不推薦)
3. 向服務端發起連接請求
4. 收發數據
5. 關閉套接字
*/
#include <iostream>
#include <signal.h>
#include <sys/wait.h>
#include "tcpsocket.hpp"
void sigcb(int no) {
//SIGCHLD信號是一個非可靠信號,有可能丟失
//因此在一次信號處理中,就需要處理到沒有子進程退出爲止
while(waitpid(-1, NULL, WNOHANG) > 0);//返回值大於0,表示有子進程退出
}
int main (int argc, char *argv[])
{
if (argc != 3) {
std::cout << "Usage: ./tcp_srv ip port\n";
return -1;
}
signal(SIGCHLD, sigcb);
std::string ip = argv[1];
uint16_t port = std::stoi(argv[2]);
TcpSocket lst_sock;
CHECK_RET(lst_sock.Socket());//創建監聽套接字
CHECK_RET(lst_sock.Bind(ip, port));//爲監聽套接字綁定地址
CHECK_RET(lst_sock.Listen());//開始監聽
while(1) {
TcpSocket new_sock;
bool ret = lst_sock.Accept(&new_sock);//通過監聽套接字獲取新建連接
if (ret == false) {
continue;//服務端不能因爲或一個新建套接字失敗就退出
}
int pid = fork();//子進程複製父進程,父進程有的子進程都有
if (pid == 0) {
while(1) {
std::string buf;
new_sock.Recv(&buf);//通過新建連接與指定客戶端進行通信
std::cout << "client say: " << buf << std::endl;
buf.clear();
std::cout << "server say: ";
std::cin >> buf;
new_sock.Send(buf);
}
new_sock.Close();//new_sock父子進程各有各的 ,父子進程數據獨有
exit(0);
}
new_sock.Close();//父進程關閉自己的不使用的socket,不影響子進程
}
lst_sock.Close();
return 0;
}