ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

Linux网络通讯(TCP)

2020-01-30 15:01:15  阅读:284  来源: 互联网

标签:socket int printf 网络通讯 TCP Linux sockfd include struct


网络通讯

TCP:

Transmission Control Protocol 传输控制协议TCP是一种面向连接(连接导向)的、可靠的、基于字节流的运输层(Transport layer)通信协议,在 OSI模型中,它完成第四层传输层所指定的功能。

在网络通讯时我们需要用到套接字,Socket(套接字)实质上提供了进程通信的端点.进程通信之前,双方首先必须各自的一个端点,否则是没有办法通信的。通过socket将IP地址和端口绑定之后,客户端就可以和服务器通信了

一、网络通讯相关函数

1、socket

函数原型

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

所需头文件

#include <sys/socket.h>
#include <sys/types.h>

返回值:
成功:返回套接字文件描述符
失败:-1

参数:
domain:通信域,确定通信特性,包括地址格式
 域        描述
AF_INET     IPv4因特网域
AF_INET6    IPv6因特网域
AF_UNIX    UNIX域
AF_UNSPEC   未指定

type:套接字类型
  类型           描述
SOCK_DGRAM      长度固定的、无连接的不可靠报文传输
SOCK_RAW        IP协议的数据报接口
SOCK_SEQPACKET   长度固定、有序、可靠的面向连接报文传递
SOCK_STREAM      有序、可靠、双向的面向连接的字节流

protocol:指定相应的传输协议,也就是诸如TCP或UDP协议等等,系统针对每一个协议簇与类型提供了一个默认的协议,我们通过把protocol设置为0来使用这个默认的值。

例如建立TCP协议的套接字

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>

int main()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1)
    {
        printf("creat socket fail\n");
        return -1;
    }
    else
    {
        printf("creat socket success,socket = %d\n",socket);
    }

    close(sockfd);
    return 0;
}

2、bind

作用:绑定服务器的地址和端口到socket,这样做就是让客户端来发现用以连接的服务器的地址

函数原型

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

所需头文件

#include <sys/socket.h>
#include <sys/types.h>

返回值:
成功:0
失败:-1

参数:
sockfd: 服务器socket所创建的文件描述符

addr: 服务器的地址,对于因特网域,如果设置地址为INADDR_ANY,套接字可以绑定到所有的网络端口。这意味着可以收到这个系统所有网卡的数据包。一般我们在使用sockaddr_in类型的结构体代替sockaddr结构体

addrlen:addr的长度

以下两者可以强制转换

struct sockaddr
{  
	unsigned   short   sa_family;
	char   sa_data[14];
};  
#include <arpa/inet.h>

struct sockaddr_in
{  
	short   int   sin_family;
	unsigned   short   int   sin_port;
	struct   in_addr   sin_addr;
	unsigned   char   sin_zero[8];
}; 

typedef struct in_addr {
  union {
       struct{
           unsigned char s_b1,
           s_b2,
           s_b3,
           s_b4;
           } S_un_b;
       struct {
           unsigned short s_w1,
           s_w2;
       } S_un_w;
       unsigned long s_addr;
  } S_un;
} IN_ADDR;

例如:

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1)
    {
        printf("creat socket fail\n");
        return -1;
    }
    else
    {
        printf("creat socket success,socket = %d\n",socket);
    }
    
    struct sockaddr_in myadd;
    myadd.sin_family = AF_INET;
    myadd.sin_port = htons(5000);
    myadd.sin_addr.s_addr = htonl(INADDR_ANY);
    
    int bindret = bind(sockfd,(struct sockaddr *)&myadd,sizeof(struct sockaddr));
    if(bindret == -1)
    {
        printf("bind fail\n");
        close(sockfd);
        return -2;
    }
    else
    {
        printf("bind success\n");
    }
    close(sockfd);
    return 0;
}

注意:IP地址通常由数字加点(192.168.0.1)的形式表示,而在struct in_addr中使用的IP地址是由32位的整数表示的,为了转换我们可以使用下面函数:

int inet_aton(const char *cp,struct in_addr *inp)
char *inet_ntoa(struct in_addr in)
unsigned long inet_addr(const char* cp);  //cp代表点分十进制的IP地址,如1.2.3.4

函数里面a 代表ascii ,n 代表network。inet_aton是将a.b.c.d形式的IP转换为32位的IP,存储在inp指针里面。inet_ntoa是将32位IP转换为a.b.c.d的格式。字节序转换

不同类型的CPU 对变量的字节存储顺序可能不同:有的系统是高位在前,低位在后,而有的系统是低位在前,高位在后,而网络传输的数据顺序是一定要是统一的。所以当内部字节存储顺序和网络字节序( big endian )不同时,就一定要进行转换。
字节序转换,32bit的整数(0x01234567)从地址0x100开始:

htons //把unsigned short类型从主机序转换到网络序
htonl //把unsigned long类型从主机序转换到网络序
ntohs //把unsigned short类型从网络序转换到主机序
ntohl //把unsigned long类型从网络序转换到主机序

3、connect

作用:客户端通过调用connect函数来建立与TCP服务器的连接。实际是发起3次握手过程

函数原型

int connect (int sockfd, struct sockaddr *addr, socklen_t addrlen);

所需头文件

#include <sys/socket.h>
#include <sys/types.h>

返回值:
成功:0
失败:-1

参数:
sockfd:socket描述符
addr:存储服务器IP地址和端口信息的结构
addrlen:地址缓冲区的长度,应该是sizeof(struct sockaddr)。

例:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1)
    {
        printf("creat socket fail\n");
        return -1;
    }
    else
    {
        printf("creat socket success,sockfd = %d\n",socket);
    }

    struct sockaddr_in cliadd;
    cliadd.sin_family = AF_INET;
    cliadd.sin_port = htons(5000);
    cliadd.sin_addr.s_addr = inet_addr("127.0.0.1");

    int con = connect(sockfd,(struct sockaddr *)&cliadd,sizeof(struct sockaddr));
    if(con == -1)
    {
        printf("client connect server fail\n");
        close(sockfd);
        return -2;
    }
    else
    {
        printf("client connect server success\n");
    }

    close(sockfd);
    return 0;
}

4、listen

作用:用来监听socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。socket()函数创建的socket默认是一个主动类型的,函数listen()会将套接口转换为被动式套接字,指示内核接受向此套接字的连接请求,调用此系统调用后tcp 状态机由close转换到listen

函数原型

int listen(int sockfd, int backlog);

所需头文件

#include <sys/socket.h>
#include <sys/types.h>

返回值:
成功:0
失败:-1

参数:
sockfd:一个套接字描述符,为要监听的socket描述字。
backlog:指定了内核为此套接字排队的最大连接个数。即当系统还没有调用accept()函数的时候,如果有很多连接,那么本地能够等待的最大数目

例:

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>

#define MAX 10

int main()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1)
    {
        printf("creat socket fail\n");
        return -1;
    }
    else
    {
        printf("creat socket success,socket = %d\n",socket);
    }
    
    struct sockaddr_in myadd;
    myadd.sin_family = AF_INET;
    myadd.sin_port = htons(5000);
    myadd.sin_addr.s_addr = htonl(INADDR_ANY);
    
    int bindret = bind(sockfd,(struct sockaddr *)&myadd,sizeof(struct sockaddr));
    if(bindret == -1)
    {
        printf("bind fail\n");
        close(sockfd);
        return -2;
    }
    else
    {
        printf("bind success\n");
    }

    int listenret = listen(sockfd,MAX);
    if(listenret == -1)
    {
        printf("listen error\n");
        close(sockfd);
        return -3;
    }
    else
    {
        printf("listen success\n");
    }

    close(sockfd);
    return 0;
}

5、accept

作用:仅被TCP类型的服务器程序调用。从已完成连接队列返回下一个建立成功的连接,如果已完成连接队列为空,线程进入阻塞态睡眠状态。如果accpet()执行成功,返回由内核生成的一个新的socket描述符(已连接套接字),用它代表与客户端的TCP连接。一个服务器通常只有1个监听套接字,它在该服务器的生命周期内一直存在。服务器内核为每一个服务端接受的客户端连接维护1个已连接套接字,用它实现数据双向通信。当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。

函数原型

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

所需头文件

#include <sys/socket.h>
#include <sys/types.h>

返回值:
成功:返回由内核生成的一个新的socket描述符(已连接套接字),用它代表与客户端的TCP连接
失败:-1

参数:
sockfd:服务器的socket()函数返回的描述符,即监听套接字
addr :输出的一个sockaddr_in变量地址,该变量用来存放发起连接请求的客户端的协议地址
addrlen:sizeof(struct sockaddr_in),作为输入时指明缓冲器的长度,作为输出时指明addr的实际长度

例:

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>

#define MAX 10

int main()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1)
    {
        printf("creat socket fail\n");
        return -1;
    }
    else
    {
        printf("creat socket success,socket = %d\n",socket);
    }
    
    struct sockaddr_in myadd,usradd;
    myadd.sin_family = AF_INET;
    myadd.sin_port = htons(5000);
    myadd.sin_addr.s_addr = htonl(INADDR_ANY);
    
    int bindret = bind(sockfd,(struct sockaddr *)&myadd,sizeof(struct sockaddr));
    if(bindret == -1)
    {
        printf("bind fail\n");
        close(sockfd);
        return -2;
    }
    else
    {
        printf("bind success\n");
    }

    int listenret = listen(sockfd,MAX);
    if(listenret == -1)
    {
        printf("listen error\n");
        close(sockfd);
        return -3;
    }
    else
    {
        printf("listen success\n");
    }
    
    int len = sizeof(struct sockaddr);
    int ret = accept(sockfd,(struct sockaddr *)&usradd,&len);
    if(ret == -1)
    {
        printf("accept fail\n");
        close(sockfd);
        return -4;
    }
    else
    {
        printf("accept success,accept's return is %d\n",ret);
    }

    close(ret);
    close(sockfd);
    return 0;
}

6、send

作用:为TCP类型的数据发送。实际是内核将用户数据拷贝至TCP套接口的发送缓冲区的过程。send先比较发送数据的长度len和套接字socket的发送缓冲的大小,若len大于发送缓冲区大小,则返回-1;否则,查看缓冲区剩余空间是否容纳得下要发送的len长度,若不够,则拷贝一部分,并返回拷贝长度,若缓冲区满,则等待发送,有剩余空间后拷贝至缓冲区

函数原型

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

所需头文件

#include <sys/socket.h>
#include <sys/types.h>

返回值:
成功:返回copy的字节数
失败:若在拷贝过程出现错误,则返回-1。如果send在等待协议发送数据时出现网络断开的情况,则会返回-1

参数:
sockfd: 发送端套接字描述符(非监听描述符)。
buf: 待发送数据的缓冲区。
len:待发送数据的字节长度。
flags:调用操作方式,一般情况下置为0。

7、recv

作用:TCP类型的数据接收。recv()先等待socket的发送缓冲中的数据被协议传送完毕,从接收缓冲区拷贝数据。阻塞模式下,recv将会阻塞到缓冲区里至少有一个字节才返回,没有数据时处于休眠状态。若非阻塞,则立即返回。

函数原型

ssize_t recv(int sockfd,void *buf,int len,int flags);

所需头文件

#include <sys/socket.h>
#include <sys/types.h>

返回值:
成功:返回拷贝的字节数
失败:-1

参数:
sockefd:接收端套接字描述符。
buf:接收缓冲区的基地址,该缓冲区用来存放接收到的数据。
len: 以字节计算的接收缓冲区长度。
flags:一般情况下置为0。

8、close

作用:关闭相应的socket套接字,将不会再允许进行读操作和写操作。任何有关对套接字描述符进行读和写的操作都会接收到一个错误

函数原型

int close(int fd);

所需头文件

#include <unistd.h>

返回值:
成功:0
失败:-1

参数:
fd:这里指文件描述符

二、代码示例

实现网络单方面通讯

服务端:

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <string.h>

#define MAX 1

int main()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1)
    {
        printf("creat socket fail\n");
        return -1;
    }
    else
    {
        printf("creat socket success,socket = %d\n",socket);
    }
    
    struct sockaddr_in myadd,usradd;
    myadd.sin_family = AF_INET;
    myadd.sin_port = htons(5000);
    myadd.sin_addr.s_addr = htonl(INADDR_ANY);
    
    int bindret = bind(sockfd,(struct sockaddr *)&myadd,sizeof(struct sockaddr));
    if(bindret == -1)
    {
        printf("bind fail\n");
        close(sockfd);
        return -2;
    }
    else
    {
        printf("bind success\n");
    }

    int listenret = listen(sockfd,MAX);
    if(listenret == -1)
    {
        printf("listen error\n");
        close(sockfd);
        return -3;
    }
    else
    {
        printf("listen success\n");
    }
    
    int len = sizeof(struct sockaddr);
    
    int ret = accept(sockfd,(struct sockaddr *)&usradd,&len);
    if(ret == -1)
    {
        printf("accept fail\n");
        close(sockfd);
        return -4;
    }
    else
    {
        printf("accept success,accept's return is %d\n",ret);
    }
    
    char buf[128];
    while(1)
    {
        memset(buf,0,128);
        int recvret = recv(ret,buf,128,0);
        if(recvret < 0)
        {
            printf("recv fail\n");
            close(ret);
            close(sockfd);
            return -5;
        }
        printf("client send to server :\n%s",buf);
        if(strncmp(buf,"end",3) == 0)
        {
            break;
        }
    }

    close(ret);
    close(sockfd);
    return 0;
}

客户端:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>

int main()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1)
    {
        printf("creat socket fail\n");
        return -1;
    }
    else
    {
        printf("creat socket success,sockfd = %d\n",socket);
    }

    struct sockaddr_in cliadd;
    cliadd.sin_family = AF_INET;
    cliadd.sin_port = htons(5000);
    cliadd.sin_addr.s_addr = inet_addr("127.0.0.1");

    int con = connect(sockfd,(struct sockaddr *)&cliadd,sizeof(struct sockaddr));
    if(con == -1)
    {
        printf("client connect server fail\n");
        close(sockfd);
        return -2;
    }
    else
    {
        printf("client connect server success\n");
    }
    
    char buf[128];
    while(1)
    {
        memset(buf,0,128);
        printf("client send to server message :\n");
        fgets(buf,128,stdin);
        send(sockfd,buf,strlen(buf),0);
        if(strncmp(buf,"end",3) == 0)
        {
            break;
        }
    }

    close(sockfd);
    return 0;
}
出类拔萃~ 发布了28 篇原创文章 · 获赞 0 · 访问量 989 私信 关注

标签:socket,int,printf,网络通讯,TCP,Linux,sockfd,include,struct
来源: https://blog.csdn.net/wfea_lff/article/details/104113640

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有