套接字通信

来源:互联网 发布:cpu一键超频软件 编辑:程序博客网 时间:2024/05/30 23:04

套接字通信模式

(1) 有连接模式:套接字的连接是不对称的,严格的区分服务进程和客户进程。

服务进程:socket()->bind()->listen()->accept()->read()->write()->...->close()

客户进程:socket()->connect()->write()->read()->...->close();

解释:

首先服务进程创建一个套接字,用socket(),然后调用bind与地址绑定,相当于给这个套接字起一个众所周知的名字让其他程序能和自己通信,因为他们没办法知道套接字本身的描述字。命名之后服务进程就开始等待客户进程的连接,为了处理多个客户进程连接的情况,用listen()创建了一个连接队列,通过accept()接收这些连接。

(注意,accept()会另外产生一个新的套接字,不是前面命名的那一个了,这个套接字只用于和刚刚accept的这个套接字通信,刚刚命名的那个套接字还在,用于和其他新的客户程序连接。)

客户进程创建一个套接字,也是用socket(),然后调用connect()连接服务器地址和端口,建立了连接之后两个进程就可以用read和write或者recv和send函数通信了。

(2) 无连接模式:对等的方式通信,不需要连接。

服务进程: socket()->bind()->recvfrom()->sendto()->...

客户进程: socket()->sendto()->recvfrom()->...->close();

服务程序同样先创建套接字,然后bind命名套接字。直接通过recvfrom和sendto进行接收和发送消息。

流套接字操作

基本函数:

#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *address, socklen_t address_len);  //sockfd是客户的套接字,address是远程套接字地址,address_len是地址的长度int listen(int socket, int backlog); //socket是要建立监听队列的套接字,backlog是监听队列允许悬挂连接请求个数int accept(int socket, struct sockaddr *address, socklen_t *address_len);//socket是用于接收的套接字,address是用来存放连接过来的客户地址的,address_len是address的长度ssize_t send(int socket, const void *buffer, size_t length, int flags); ssize_t recv(int socket, void *buffer, size_t length, int flags);
(1)connect函数调用成功时,sockfd被连接到了address给出的地址,成功返回0,错误返回-1,置errno。如果当前不能立即连接,connect会阻塞,也可以设置非阻塞方式。

(2)listen为socket建立一个监听队列,成功返回0,失败返回-1并置error

(3)accept用于接收在服务套接字socket上的连接请求。除非socket是非阻塞的,不然会阻塞直到有客户连接。accept返回新的套接字,原来的套接字socket用于接收别的连接

(4)send类似write函数,但是send只能用于套接字,作用是向已经连接的套接字发送数据。flag可以为0(同write函数),MSG_OOB(带外数据),MSG_DONTROUTE(不在消息中包含路由信息).阻塞的,直到数据都被传送完了才返回。

(5)recv类似read函数,作用是从已连接的套接字中接收消息。flag可以为0(同read函数),MSG_PEEK(窥视套接字上的数据不实际读出他们,buffer所指对象会填入这些数据,但是后面的read或recv会读到同样的数据),MSG_OOB(带外数据),MSG_WAITALL(阻塞直至接收到所请求的全部数据)。是阻塞的,直到套接字上有消息到达。

补充:

带外数据是流套接字特有的,由于流套接字传送数据时,接收端必须依次读套接字上的当前数据,所以当出现一个紧急情况时,没有办法立即通知接收进程。带外数据就是在正常的流之外发送,可以越过正常的数据,到达接收进程时,接收进程会获得一个信号,立即处理这个数据。

socket与三次握手(以下来自http://blog.csdn.net/gneveek/article/details/8699198)

我们知道tcp建立连接要进行“三次握手”:

image

从图中可以看出,当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。

总结:客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。

socket与四次握手(以下来自http://blog.csdn.net/gneveek/article/details/8699198)

image

  • 某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;
  • 另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;
  • 一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;
  • 接收到这个FIN的源发送端TCP对它进行确认。

这样每个方向上都有一个FIN和ACK。

一个简单的服务器和客户端。服务器把当前时间发给客户端。

server:

/*************************************************************************> File Name: server.cpp> Author: > Mail: > Created Time: 2017年11月20日 星期一 14时47分47秒 ************************************************************************/#include<iostream>using namespace std;#include <stdio.h>#include <stdlib.h>#include <sys/socket.h>#include <netinet/in.h>#include <netdb.h>#include <arpa/inet.h>#include <unistd.h>#include <string.h>#define LISTENQ 5#define MAXLINE 512int make_socket(int type, unsigned short int port){    int sock;    struct sockaddr_in name;    /*创建套接字*/    if((sock = socket(AF_INET, type, 0)) < 0){        printf("Socket error\n");        exit(-1);    }    /*命名该套接字*/    name.sin_family = AF_INET;    name.sin_port = htons(port);    name.sin_addr.s_addr = htonl(INADDR_ANY);    if(bind(sock, (struct sockaddr *)&name, (socklen_t)sizeof(name)) < 0)    {        printf("bind error\n");        exit(-1);    }    return sock;}int main(int argc, char **argv){    int listenfd, connfd;    socklen_t len;    struct sockaddr_in servaddr, cliaddr;    char buff[MAXLINE];    time_t ticks;    /*创建然后命名套接字*/    listenfd = make_socket(SOCK_STREAM, 5013);    listen(listenfd, LISTENQ);    /*连接处理*/    for(; ;)    {        len = sizeof(cliaddr);        //accept        connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &len);        printf("connect from %s, port %d\n", inet_ntoa((struct in_addr)cliaddr.sin_addr), ntohs(cliaddr.sin_port));        ticks = time(NULL);        sprintf(buff, "%.24s\r\n", ctime(&ticks));        write(connfd, buff, strlen(buff));        close(connfd);    }}

client:

/*************************************************************************> File Name: client.cpp> Author: > Mail: > Created Time: 2017年11月20日 星期一 14时48分10秒 ************************************************************************/#include<iostream>#include <stdio.h>#include <string.h>#include <stdlib.h>#include <unistd.h>#include <sys/socket.h>#include <netinet/in.h>#include <netdb.h>#include <arpa/inet.h>#include <unistd.h> //gethostname()#define MAXBUFFSIZE 256using namespace std;/** * 根据给出的主机名获得主机的IP地址信息,填充在参数sockaddr_in中 *  * */void init_sockaddr(struct sockaddr_in *name, const char *hostname, const char *port){    struct hostent *hp;             //表示主机网络地址数据库中的一个登记项    char *host, myname[255];    if(hostname == NULL){        gethostname(myname, sizeof(myname));        host = myname;    }    else{        //host = hostname;        strcpy(host, hostname);    }    /*获取主机地址信息*/    if((hp = gethostbyname(host)) == NULL){        printf("Unknown host : %s \n", host);        exit(-1);    }    /*填入服务器的IP地址和端口*/    bzero(name, sizeof(struct sockaddr)); // ???    if(hp->h_addrtype == AF_INET){        name->sin_family = AF_INET;        bcopy(hp->h_addr_list[0], &name->sin_addr, hp->h_length);        if(port == NULL){            name->sin_port = htons(0);        }        else{            name->sin_port = htons(atoi(port));        }    }    else{        cout << "Unknown  address type\n" << endl;        exit(-1);    }        }int socket_connect(char *hostname, char *serv_port){    int sockfd;    struct sockaddr_in saddr;    struct hostent *hp;    /*获取服务器信息*/    init_sockaddr(&saddr, hostname, serv_port);    /*创建套接字*/    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)    {        printf("socket error");        exit(-1);    }    /*连接服务器*/    if(connect(sockfd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0)    {        printf("connect error\n");        exit(-1);    }    return sockfd;}int main(int argc, char **argv){    int sockfd, n;    char recvbuff[MAXBUFFSIZE], *host;    struct sockaddr_in servaddr;    if(argc < 2){        host = NULL;    }    else{        host = argv[1];    }    sockfd = socket_connect(host, "5013");    /*读服务的回答并显示*/    while((n = read(sockfd, recvbuff, MAXBUFFSIZE)) > 0){        recvbuff[n] = 0;        fputs(recvbuff, stdout);    }    if(n < 0){        printf("read error");        exit(-1);    }    return 0;}


原创粉丝点击