套接字通信

套接字通信模式

(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 512
int 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 256
using 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;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python套接字通信是指使用Python编程语言中的套接字socket)库进行网络通信的过程。套接字网络编程中常用的一种实现方式,它提供了在网络上进行数据传输的接口。Python的套接字库包括了一系列函数和类,可以用于创建和管理套接字对象,并实现数据的发送和接收。 在Python中,套接字通信的基本流程通常包括以下几个步骤: 1. 导入套接字库:首先需要在程序中导入socket模块,即使用`import socket`语句。 2. 创建套接字对象:使用`socket.socket()`函数创建一个套接字对象,该函数有两个参数,分别是套接字的地址族和套接字的类型。常见的地址族有AF_INET(IPv4)和AF_INET6(IPv6),常见的套接字类型有SOCK_STREAM(TCP)和SOCK_DGRAM(UDP)。 3. 绑定套接字到地址和端口:使用`bind()`方法将套接字绑定到指定的地址(IP地址)和端口号,以便在该地址和端口上进行监听和通信。 4. 监听连接请求(对于TCP通信):使用`listen()`方法开始监听来自客户端的连接请求。该方法有一个参数,表示最大连接数。 5. 接受连接请求(对于TCP通信):使用`accept()`方法接受来自客户端的连接请求,并返回一个新的套接字对象和客户端的地址信息。 6. 发送和接收数据:使用新的套接字对象进行数据的发送和接收。可以使用`send()`和`recv()`方法分别发送和接收数据。也可以使用`sendall()`方法连续发送所有数据,并自动处理数据的分割和传输。对于TCP通信,数据是可靠的,保证按发送的顺序接收。对于UDP通信,数据是不可靠的,可能丢失或乱序。 7. 关闭套接字:使用`close()`方法关闭套接字,释放资源。 套接字通信可以用于实现多种应用,例如客户端-服务器模式的网络通信、文件传输、网络游戏、远程控制等。Python中的套接字库提供了丰富的功能和方法,使得网络通信编程变得简单和方便。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值