关闭

关于socket

632人阅读 评论(0) 收藏 举报
分类:

1.TCP/IP和UDP区别

TCP是可靠地,如果这个通道坏了,会有其他方法修复或者重建通道,消息太多时也会有办法去处理溢出的信息。
UDP来说,设计角度就是不可靠的,省略其中很多流程,得到较快的速度,数据的可靠性由上层协议即应用层来保证,消息是否传达也无法做出保证。

TCP/IP模型里面,网络分为五层:从上往下是应用层,传输层,网络层,数据链路层,物理层。
*物理层:通信的物理条件。
*数据链路层:队伍里信号的第一次处理,保证这些数字信号能够可靠的发送到目标的网络层。
接收端保证数据的可靠性,校验算法,对数据进行分组,将IP地址转换成MAC地址(48位的二进制串,一台机器的物理地址)
*网络层:对数据从传输的速率优化算法和协议,主要作用是转发和选路,路由器,IP协议在这一层。
*传输层:两个最主要的协议就是TCP协议和UDP协议。
*应用层:最常见的协议是HTTP(基于SSL的协议,安全性较高),DNS(域名系统)在此层。(程序员工作的部分,socket工作在此层)

TCP是可靠的,因为TCP协议要做流量控制,保证数据传输的可靠性,连接管理,拥塞控制等,而这些UDP都没有,但是优点是比较快,对于需要快并且信息不重要的场合如qq比较合适。

2.同步、异步、阻塞、非阻塞的理解

同步:

在发出一个功能调用时,在没有得到结果之前,该调用就不返回。按照这个定义,其实绝大多数函数都是同步调用。但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。最常见的例子就是SendMessage。该函数发送一个消息给某个窗口,在对方处理完消息之前,这个函数不返回。当对方处理完毕以后,该函数才把消息处理函数所返回值返回给调用者。

异步:

异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。当一个客户端通过调用 Connect函数发出一个连接请求后,调用者线程立刻可以向下运行。当连接真正建立起来以后,socket底 层会发送一个消息通知该对象。

阻塞:

阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。看到这里也许会把阻塞调用和同步调用等同起来,实际上它们是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。例如,我们在CSocket中调用Receive函数,如果缓冲区中没有数据,这个函数就会一直等待,直到有数据才返回。而此时,当前线程还会继续处理各种各样的消息。socket接收数据的另外一个函数recv则是一个阻塞调用的例子。当socket工作在阻塞模式的时候, 如果没有数据的情况下调用该函数,则当前线程就会被挂起,直到有数据为止。

非阻塞:

非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。对象的阻塞模式和阻塞函数调用对象是否处于阻塞模式和函数是不是阻塞调用有很强的相关性,但是并不是一 一对应的。

阻塞就是干不完不准回来,一直处于等待中,直到事情处理完成才返回;
非阻塞就是你先干,我先看看有其他事没有,一发现事情被卡住,马上报告领导。
阻塞对象上可以有非阻塞的调用方式,我们可以通过一定的API去轮询状态,在适当的时候调用阻塞函数,就可以避免阻塞。而对于非阻塞对象,调用特殊的函数也可以进入阻塞调用。函数select就是这样的一个例子。

socket编程的过程和工作原理:

Socket是进程通讯的一种方式,即调用这个网络库的一些API函数。Socket可以理解为客户端或者服务器端的一个特殊的对象,这个对象有两个关键的方法,一个是getInputStream方法,另一个是getOutputStream方法。getInputStream方法可以得到一个输入流,客户端的Socket对象上的getInputStream方法得到的输入流其实就是从服务器端发回的数据流。GetOutputStream方法得到一个输出流,客户端Socket对象上的getOutputStream方法返回的输出流就是将要发送到服务器端的数据流客户基于服务器之间使用的大部分通讯组件都是基于socket接口来实现的。
这里写图片描述

客户段程序编写的流程:

1、 首先调用Socket类的构造函数,以服务器的指定的IP地址或指定的主机名和指定的端口号为参数,创建一个Socket流,在创建Socket流的过程中包含了向服务器请求建立通讯连接的过程实现。
  2、 建立了客户端通讯Socket后。就可以使用Socket的方法getInputStream()和getOutputStream()来创建输入/输出流。这样,使用Socket类后,网络输入输出也转化为使用流对象的过程。
  3、 使用输入输出流对象的相应方法读写字节流数据,因为流连接着通讯所用的Socket,Socket又是和服务器端建立连接的一个端点,因此数据将通过连接从服务器得到或发向服务器。这时我们就可以对字节流数据按客户端和服务器之间的协议进行处理,完成双方的通讯任务。
  4、 待通讯任务完毕后,我们用流对象的close()方法来关闭用于网络通讯的输入输出流,在用Socket对象的close()方法来关闭Socket。

关于原理

TCP/IP

这里写图片描述
服务器必须首先启动,直到它执行完accept()调用,进入等待状态后,方能接收客户请求。假如客户在此前启动,则connect()将返回出错代码,连接不成功。
###UDP
无连接服务器也必须先启动,否则客户请求传不到服务进程。无连接客户不调用connect()。因此在数据发送之前,客户与服务器之间尚未建立完全相关,但各自通过socket()和bind()建立了半相关。发送数据时,发送方除指定本地套接字号外,还需指定接收方套接字号,从而在数据收发过程中动态地建立了全相关。

socket基本函数及应用

先说明头文件# include < WinSock2.h >(windows下)
基本学习的函数如下:socket、bind、listen、connect、accept、send、recv、recvfrom、close、shutdown

创建套接字──socket()

int socket(int domain,int type,int protocol)

返回值:

非负为成功,-1为失败。

参数:

1.指定通信发生的区域 (Windows下仅支持AF_INET)
2.套接口类型,主要SOCK_STREAM、SOCK_DGRAM、SOCK_RAW;
3.protocol一般取为0。成功时,返回一个小的非负整数值,与文件描述符类似.
eg:
sock = socket(AF_INET, SOCK_STREAM, 0);  
if (sock < 0) 
{   
    perror(“opening stream socket”);   
    exit(1); 
}  

指定本地地址──bind()

Int bind( IN SOCKET s, IN const struct sockaddr FAR * name, IN int namelen);
// IN windows的宏,表示输入

返回值0为成功,-1为失败

当socket函数返回一个描述符时,只是存在于其协议族的空间中,并没有分配一个具体的协议地址(这里指IPv4/IPv6和端口号的组合),bind函数可以将一组固定的地址绑定到sockfd上。

参数:

1.是socket函数返回的描述符;
2.指定了想要绑定的IP和端口号,均要使用网络字节序-即大端模式;
3.是前面struct sockaddr(与sockaddr_in等价)的长度。

为了统一地址结构的表示方法,统一接口函数,使得不同的地址结构可以被bind()、connect()、recvfrom()、sendto()等函数调用。但一般的编程中并不直接对此数据结构进行操作,而使用另一个与之等价的数据结构sockaddr_in。

if (bind(sock, (struct sockaddr *)&server, sizeof(server)) < 0)
{   
    perror(“binding stream socket”);   
    exit(1);   
} 

监听连接──listen()

int listen(int sockfd,int backlog)
此调用用于面向连接服务器,表明它愿意接收连接。listen()需在accept()之前调用

返回值:

0成功,-1失败

参数:

1、一个本地已建立、尚未连接的套接字号,服务器愿意从它上面接收请求
2、表示请求连接队列的最大长度,用于限制排队请求的个数,目前允许的最大值为5

  if (listen(initsockid , 5) < 0)  
   error(“listen error”);     

建立套接字连接──connect()与accept()

这两个系统调用用于完成一个完整相关的建立,其中connect()用于建立连接。无连接的套接字进程也可以调用connect(),但这时在进程之间没有实际的报文交换,调用将从本地操作系统直接返回。这样做的优点是程序员不必为每一数据指定目的地址,而且如果收到的一个数据报,其目的端口未与任何套接字建立“连接”,便能判断该端便能判断该端口不可操作。
int connect(int sockfd,conststruct sockaddr *addr, socklen_t addrlen)

返回值:

0成功,-1失败

参数:

1.是欲建立连接的本地套接字描述符。
2.指出说明对方套接字地址结构的指针
3.对方套接字地址长度由namelen说明

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)

返回值:

非负数为成功,-1失败

参数:

1.为本地套接字描述符,在用做accept()调用的参数前应该先调用过listen()
2.指向客户方套接字地址结构的指针,用来接收连接实体的地址。addr的确切格式由套接字创建时建立的地址族决定。
3.为客户方套接字地址的长度(字节数)。

数据传输──send()与recv()

int send(IN SOCKET s, IN const char FAR * buf, IN int len, IN int flags)

返回值:

成功拷贝至发送缓冲区的字节数(可能小于len),-1出错,并置错误号errno.

参数:

1、为已连接的本地套接字描述符
2、指向存有发送数据的缓冲区的指针
3、buf长度由len 指定
4、指定传输控制方式,如是否发送带外数据等

int recv(int sockfd,void *buf, size_t len,int flags)

返回值:

成功时,返回拷贝的字节数,失败返回-1

参数:

1、为已连接的套接字描述符
2、指向接收输入数据缓冲区的指针
3、buf长度由len 指定
4、指定传输控制方式,如是否接收带外数据等

迷之相似,只有过程的发出者相对不同

输入/输出多路复用──select()

关闭套接字──closesocket()

// PS: struct sockaddr{ u_short sa_family; char sa_data[14];};
// sa_family指定该地址家族, sa_data起到占位占用一块内存分配区的作用
// 在TCP/IP中,可使用sockaddr_in结构替换sockaddr,以方便填写地址信息
// struct sockaddr_in{ short sin_family; unsigned short sin_port; struct in_addr sin_addr; char sin_zero[8];};
// sin_family表示地址族,对于IP地址,sin_family成员将一直是AF_INET。
// sin_port指定将要分配给套接字的端口。
// sin_addr给出套接字的主机IP地址。
// sin_zero[8]给出填充数,让sockaddr_in与sockaddr结构的长度一样。
// 将IP地址指定为INADDR_ANY,允许套接字向任何分配给本地机器的IP地址发送或接收数据。
// 如果想只让套接字使用多个IP中的一个地址,可指定实际地址,用inet_addr()函数。

代码

服务器端:

// server.cpp

#include <iostream>
#include <cstdio>
#include <Winsock2.h>

using namespace std;

int main()
{
// 加载socket动态链接库(dll)
    WORD wVersionRequested;
    WSADATA wsaData;    // 这结构是用于接收Wjndows Socket的结构信息的
    int err;

    wVersionRequested = MAKEWORD( 1, 1 );   // 请求1.1版本的WinSock库

    err = WSAStartup( wVersionRequested, &wsaData );
    if ( err != 0 ) {
        return -1;          // 返回值为零的时候是表示成功申请WSAStartup
    }

    if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) {
        // 检查这个低字节是不是1,高字节是不是1以确定是否我们所请求的1.1版本
        // 否则的话,调用WSACleanup()清除信息,结束函数
        WSACleanup( );
        return -1; 
    }

// 创建socket操作,建立流式套接字,返回套接字号sockSrv
// 套接字sockSrv与本地地址相连


    SOCKADDR_IN addrSrv;
    addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 将INADDR_ANY转换为网络字节序,调用 htonl(long型)或htons(整型)
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(6000);

    bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); // 第二参数要强制类型转换

// 将套接字设置为监听模式(连接请求), listen()通知TCP服务器准备好接收连接
    listen(sockSrv, 10);

// accept(),接收连接,等待客户端连接 send(), 在套接字上发送数 recv(), 在套接字上接收数据
    SOCKADDR_IN  addrClient;
    int len = sizeof(SOCKADDR);

    while(true){    // 不断等待客户端请求的到来
        SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrClient, &len);

        char sendBuf[100];
        sprintf(sendBuf, "Welcome %s to the server program~ \nNow, let's start talking...\n", inet_ntoa(addrClient.sin_addr));
        send(sockConn, sendBuf, strlen(sendBuf)+1, 0);  // 发送显示欢迎信息

        char recvBuf[100];
        recv(sockConn, recvBuf, 100, 0);
        printf("%s\n", recvBuf);        // 接收第一次信息

        char * sockConnName = "Client";
        printf("我们可以聊五句话");
        int n = 5;
        while(n--){
            printf("还剩%d次:\n", n+1);
            char recvBuf[100];
            recv(sockConn, recvBuf, 100, 0);
            printf("%s Says: %s\n", sockConnName, recvBuf);     // 接收信息

            char talk[100];
            printf("Please enter what you want to say next(\"quit\"to exit):");
            gets(talk);
            send(sockConn, talk, strlen(talk)+1, 0);            // 发送信息
            printf("\n");
        }       
        printf("\nEnd talking... \n");
        closesocket(sockConn);
    }

    printf("\n");
    system("pause");
    return 0;
}

客户端:

#include <iostream>
#include <cstdio>
#include <Winsock2.h>

using namespace std;

int main()
{
// 加载socket动态链接库(dll)
    WORD wVersionRequested;
    WSADATA wsaData;    // 这结构是用于接收Wjndows Socket的结构信息的
    int err;

    wVersionRequested = MAKEWORD( 1, 1 );   // 请求1.1版本的WinSock库

    err = WSAStartup( wVersionRequested, &wsaData );
    if ( err != 0 ) {
        return -1;          // 返回值为零的时候是表示成功申请WSAStartup
    }

    if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) {
        // 检查这个低字节是不是1,高字节是不是1以确定是否我们所请求的1.1版本
        // 否则的话,调用WSACleanup()清除信息,结束函数
        WSACleanup( );
        return -1; 
    }

// 创建socket操作,建立流式套接字,返回套接字号sockClient

// 将套接字sockClient与远程主机相连

    SOCKADDR_IN addrSrv;
    addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");      // 本地回路地址是127.0.0.1; 
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(6000);
    connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

    char recvBuf[100];
    recv(sockClient, recvBuf, 100, 0);
    printf("%s\n", recvBuf);

    send(sockClient, "Attention: A Client has enter...\n", strlen("Attention: A Client has enter...\n")+1, 0);

    printf("我们可以聊五句话");
    int n = 5;
    do{
        printf("\n还剩%d次:", n);
        char talk[100];
        printf("\nPlease enter what you want to say next(\"quit\"to exit):");
        gets(talk);
        send(sockClient, talk, strlen(talk)+1, 0);          // 发送信息

        char recvBuf[100];
        recv(sockClient, recvBuf, 100, 0);
        printf("%s Says: %s\n", "Server", recvBuf);     // 接收信息
    }while(--n);

    printf("End linking...\n");
    closesocket(sockClient);
    WSACleanup();   // 终止对套接字库的使用

    printf("\n");
    system("pause");
    return 0;
}

很遗憾。。。我并没有在cb上运行下来。。。。加上ws_32.lib和WinSocket这两个lib之后虽然不在报错,但是并没有达到预期的效果。
无奈之下,改用ubuntu,试图在虚拟机下跑程序,从装VM到Vbox各种崩溃,最后看到正常的界面要感动哭了,只写了最简单的聊天室,命令行也一点一点百度,改天在写博客吧。。看到2:25我也是蛮拼的,最后还是没能实现全双工通信0_0

serve端:
这里写图片描述
这里写图片描述
这里写图片描述

client端:
这里写图片描述
这里写图片描述
这里写图片描述

ending。。。貌似也不算,没有聊起来天很遗憾那
这里写图片描述
这里写图片描述

写这个程序的时候真的费了很大的劲,开两个终端这件事并不知道,感觉学的飘起来了,并不知道低层建筑,好好看书吧,时间有点紧,这周课好多,都是白天时间不够,晚上熬夜来凑
解释下argv那个参数返回,刚开始都没搞明白,后来理解因为argv != 2 那句话
./client是不够的,因为这个就时参数少,要写上地址,就是后来输入的127.0.0.1;
事实证明深入理解新学的程序是需要尝试的,多跑代码,多改变不同的东西才能理解吧,祝福自己。。。

2
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:28497次
    • 积分:1904
    • 等级:
    • 排名:千里之外
    • 原创:168篇
    • 转载:0篇
    • 译文:0篇
    • 评论:11条
    最新评论