Linux 5.socket网络编程

socket概述

套接字是通信的基石,是支持TCP/IP协议的路通信的基本操作单元。可以将套接字看作不同主机间的进程进行双间通信的端点,它构成了单个主机内及整个网络间的编程界面。套接字存在于通信域中,通信域是为了处理一般的线程通过套接字通信而引进的一种抽象概念。套接字通常和同一个域中的套接字交换数据(数据交换也可能穿越域的界限,但这时一定要执行某种解释程序),各种进程使用这个相同的域互相之间用Internet协议簇来进行通信。

Socket(套接字)可以看成是两个网络应用程序进行通信时,各自通信连接中的端点,这是一个逻辑上的概念。它是网络环境中进程间通信的API(应用程序编程接口),也是可以被命名和寻址的通信端点,使用中的每一个套接字都有其类型和一个与之相连进程。通信时其中一个网络应用程序将要传输的一段信息写入它所在主机的 Socket中,该 Socket通过与网络接口卡(NIC)相连的传输介质将这段信息送到另外一台主机的 Socket中,使对方能够接收到这段信息。 Socket是由IP地址和端口结合的,提供向应用层进程传送数据包的机制。

个人理解:socket其实就是一根通信电缆两端的电话终端,电话接通后就相当两个socket建立了连接,两个电话之间可以相互通话,两个socket之间就可以实时收发数据,socket仅仅是一个通信工具,通信工具,通信工具重要的事说三遍(OSI模型中的第四层传输层的API接口,这一层通常使用两种协议TCP或UDP来传输)并不是一种协议。TCP、UDP、HTTP才是我们通常理解的协议。

也就是说,Socket这个工具一般使用TCP和UDP两种协议来通信,否则光杆socket并没有毛用。其实我们所认识到的互联网中的各种通信:web请求、即时通讯、文件传输和共享等等底层都是通过Socket工具来实现的,所以说互联网一切皆socket。搞懂了socket你就相当于打通了任督二脉。

作者:打火石
链接:https://www.jianshu.com/p/aa8c1564ef7b
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

TCP/UDP对比

  1. TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据前,不需要建立连接。
  2. TCP提供可靠的服务,也就是说通过TCP连接传送的数据是无差错,不丢失,不重复且按序到达的;UDP是尽最大努力交付,即保证可靠交付。
  3. TCP是面向字节流,实际上是TCP把数据看成是一连串无结构的字节流;UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会是源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议…)。
  4. 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。
  5. TCP的首部开销20字节;UDP的首部开销小,只有8个字节。
  6. TCP是逻辑通信信道是全双工的可靠信道;UDP是不可靠信道。

端口号作用

一台拥有 IP 地址的主机可以提供许多服务,比如 Web 服务、FTP 服务、 SMTP 服务等。
这些服务完全可以通过 1 个 IP 地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠 IP 地址,因为 IP 地址与网络服务的关系是一对多的关系。
实际上是通过“ IP 地址 + 端口号”来区分不同的服务的。端口提供了一种访问通道,服务器一般都是通过知名端口号来识别的。例如,对于每个 TCP/IP 实现来说,FTP服务器的 TCP 端口号都是 21 ,每个 Telnet 服务器的 TCP端口号都是 23 ,每个 TFTP( 简单文件传送协议 ) 服务器的 UDP 端口号都是 69 。

字节序

字节序,即字节在电脑中存放时的序列与输入(输出)时的序列是先到的在前还是后到的在前。
常用字节序:
7. 小段字节序(Little endian):将低序字节存储在起始地址
8. 大端字节序(Big endian):将高序字节存储在起始地址
在这里插入图片描述

sockt 服务器和客户端的开发步骤

在这里插入图片描述
9. 创建套接字
10. 为套接字添加信息(IP地址和端口号)
11. 监听网络连接
12. 监听到有客户端接入,接受一个连接
13. 数据交互
14. 关闭套接字,断开连接

socket()函数介绍

socket()

功能:socket()函数用于根据指定的地址族、数据类型和协议来分配一个套接口的描述字及其所用的资源。

头文件

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>	

socket()函数原型

int socket(int domain, int type, int protocol);
int domain:指明所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族)
		AF_UNIX, AF_LOCAL   Unix域
		AF_INET             IPv4 因特网域
		AF_INET6            IPv6 因特网域
		AF_ROUTE 		    路由套接字
		AF_KEY			    密钥套接字
		AF_UNSPEC		    未指定

int type:指定socket的类型
		SOCK_STREAM:流式套接字提供的可靠的,面向连接的通信流;它使用TCP协议,从而保证了数据传输的正确性和顺序性
		SOCK_DGRAM:数据报套接字定义了一种无连接的服务,数据报通过相互独立的报文进行传输,是无序的,并且不保证是可靠,无差错的。它使用的数据报协议是UDP
		SOCK_RAW:允许程序使用底层协议,原始套接字允许对底层协议如IP或ICMP进行直接访问,功能强大但是使用较为不方便,主要用于一些协议的开发。

int protocol:通常赋值为00:表示自动选择type类型对应的默认协议
		IPPROTO_TCP    TCP传输协议
		IPPROTO_UDP    UDP传输协议
		IPPROTO_SCTP   SCTP传输协议
		IPPROTO_TICP   TICP传输协议

bind()

功能:用于绑定IP地址和端口号到 socketfd

头文件

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

bind()函数原型

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

int sockfd:一个socket描述符

const struct sockaddr *addr:是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的结构体指针,指向要绑定给sockfd的协议地址结构,这个地址结构根据地址创建socket时的地址协议族的不同而不同
struct sockaddr {
               sa_family_t sa_family;//协议族
               char        sa_data[14];//IP+端口号
}//可同等替换为下面这个
struct sockaddr_in {//如何找到这个结构体,在下方有详解
  					__kernel_sa_family_t  sin_family; //协议族
 					__be16                sin_port; //端口号     
  					struct in_addr        sin_addr;//IP地址结构体 ,   
 					unsigned char         __pad[__SOCK_SIZE__ - sizeof(short int) -sizeof(unsigned short int) - sizeof(struct in_addr)];/*填充 没有实际意义,只是为跟sockaddr结构在内存在内存中对其,这样两者才能相互*/
};

socklen_t addrlen:数据大小(sizeof(struct sockaddr_in))

如何找到到struct sockaddr_in这个结构体和struct in_addr sin_addr这个结构体

  1. 在命令终端窗口输入 cd /usr/include/
  2. 在命令终端窗口输入 grep “struct sockaddr_in {” * -nir (n表示显示行号,i表示不区分大小写)
  3. 在命令终端窗口输入 vi linux/in.h +184
    在这里插入图片描述
    在这里插入图片描述
    找struct in_addr sin_addr这个结构体也是用同样的方法。

在这里插入图片描述
在这里插入图片描述
地址转换API

int inet_aton(const char *cp, struct in_addr *inp);
把字符串形式的IP地址如"192.168.1.123"装换为网络能识别的格式
const char *cp:为你的IP地址
struct in_addr *inp:存放你这个IP底盘指针结构体(在上面bind()中有这个结构体),例如:&s_addr
char *inet_ntoa(struct in_addr inaddr);
把网络格式的IP地址转换成字符串形式
ex:inet_ntoa(c_addr.sin_addr)

listen()

功能:监听设置函数,只能用于服务端,为了接受连接,先用socket()创建一个套接口的描述字,然后用listen()创建套接口并为申请进入的连接建立一个后备日志,然后便可用accept()接受连接了。listen()仅适用于支持连接的套接口,如SOCK_STREAM类型的。套接口s处于一种“变动”模式,申请进入的连接请求被确认,并排队等待被接受。这个函数特别适用于同时有多个连接请求的服务器;如果当一个连接请求到来时,队列已满,那么客户将收到一个WSAECONNREFUSED错误。

当没有可用的描述字时,listen()函数仍试图正常地工作。它仍接受请求直至队列变空。当有可用描述字时,后续的一次listen()或accept()调用会将队列按照当前或最近的“后备日志”重新填充,如有可能的话,将恢复监听申请进入的连接请求。

头文件

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

listen()函数原型

int listen(int sockfd, int backlog);
int sockfd:是一个socket描述符
int backlog:等待连接队列的最大长度。

accept()

功能:accept函数由 TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠。

头文件

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

accept()函数原型

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

int sockfd:是一个socket描述符
struct sockaddr *addr:用来返回已连接的对端(客户端)的协议地址,也就是客户端地址,(struct sockaddr *)&c_addr
socklen_t *addrlen:客户端地址长度, int clen = sizeof(struct sockaddr_in);

connect()

功能:客户端连接主机。

头文件

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

connect()函数原型

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

int sockfd:目的服务器的文件描述符
const struct sockaddr *addr:是服务器端的IP地址和端口号的地址结构指针,(struct sockaddr *)&c_addr
socklen_t addrlen:地址长度,常被设置为sizeof(struct sockaddr_in)

字节序转换API

头文件

#include <arpa/inet.h>

函数原型

uint32_t htonl(uint32_t hostlong);//返回网络字节序的值
uint16_t htons(uint16_t hostshort);//返回网络字节序的值
uint32_t ntohl(uint32_t netlong);//返回主机字节序的值
uint16_t ntohs(uint16_t netshort);//返回主机字节序的值

/*h代表host,n代表net,s代表short(两个字节),l代表long(四个字节),通过上面四个函数可以实现主机和网络字节序的之间的转换。有时可以用INADDR_ANY,INADDR_ANY指定地址让操作系统之间获取*/

代码示例

sever1

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

int main(int argc,char **argv)
{
        //1.socket
        int s_fd;
        int c_fd;
        int n_read;
        char readBuf[128];
        char writeBuf[128]={0};
        struct sockaddr_in s_addr;
        struct sockaddr_in c_addr;

        memset(&s_addr,0,sizeof(struct sockaddr_in));
        memset(&c_addr,0,sizeof(struct sockaddr_in));

        if(argc!=3)
        {
                printf("param is not good!\n");
                exit(-1);
        }

        s_fd=socket(AF_INET,SOCK_STREAM,0);
        if(s_fd==-1)
        {
                perror("socket");
                exit(-1);
        }

        s_addr.sin_family=AF_INET;
        s_addr.sin_port=htons(atoi(argv[2]));
        inet_aton(argv[1],&s_addr.sin_addr);
        //2.bind
		bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
        //3.listen
        listen(s_fd,10);
        //4.accept

        //      int accept4(int sockfd, struct sockaddr *addr,socklen_t *addrlen, int flags);
        int addrlen=sizeof(struct sockaddr_in);

        while(1)
        {
                c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&addrlen);
                if(c_fd==-1)
                {
                        perror("accept");
                }

                printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
                //5.read
                if(fork()==0)
                {
                        if(fork()==0)
                        {
                                while(1)
                                {
                                        memset(writeBuf,0,sizeof(writeBuf));
                                        printf("Input:");
                                        gets(writeBuf);
                                        write(c_fd,writeBuf,strlen(writeBuf));
                                }
                        }
                //6.write
                //      ssize_t write(int fd, const void *buf, size_t count);
                        while(1)
                        {
                                //      ssize_t read(int fd, void *buf, size_t count);
                                memset(&readBuf,0,sizeof(readBuf));
                                n_read=read(c_fd,readBuf,128);
                                if(n_read==-1)
                                {
                                        perror("read");
                                }
                                else
                                {
                                        printf("get message:%d,%s\n",n_read,readBuf);
                                }
                        }
                        break;
                }
        }
        return 0;
}

client1

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>

int main(int argc,char **argv)
{
        int c_fd;
        int n_read;
        int connect_fd;
        char msg[128]={0};
        char readBuf[128];
        struct sockaddr_in c_addr;

        memset(&c_addr,0,sizeof(struct sockaddr_in));

        if(argc!=3)
        {
                printf("param is not good!");
                exit(-1);
        }

        //1.socket
        c_fd=socket(AF_INET,SOCK_STREAM,0);
        if(c_fd==-1)
        {
                perror("socket");
                exit(-1);
        }

        c_addr.sin_family=AF_INET;
        c_addr.sin_port=htons(atoi(argv[2]));
        inet_aton(argv[1],&c_addr.sin_addr);

        //2.connect
        //      int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
        connect_fd=connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in));
        if(connect_fd==-1)
        {
                perror("connect");
                exit(-1);
        }

        while(1)
        {
                if(fork()==0)
                {
                        //3.send
                        while(1)
                        {
                                memset(msg,0,sizeof(msg));
                                printf("Input:");
                                gets(msg);
                                write(c_fd,msg,strlen(msg));
                        }
                }
                        //4.read
                while(1)
                {
                        memset(&readBuf,0,sizeof(readBuf));
                        n_read=read(c_fd,readBuf,128);
                        if(n_read==-1)
                        {
                                perror("read");
                        }
                        else
                        {
                                printf("client gets message from sever:%d,%s\n",n_read,readBuf);
                        }
                }
        }
        return 0;
}

运行结果

在这里插入图片描述
在这里插入图片描述

sever2

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

int main(int argc,char **argv)
{
        //1.socket
        int s_fd;
        int c_fd;
        int n_read;
        int mark=0;
        char readBuf[128];
        char writeBuf[128]={0};
        struct sockaddr_in s_addr;
        struct sockaddr_in c_addr;

        memset(&s_addr,0,sizeof(struct sockaddr_in));
        memset(&c_addr,0,sizeof(struct sockaddr_in));

        if(argc!=3)
        {
                printf("param is not good!\n");
                exit(-1);
        }

        s_fd=socket(AF_INET,SOCK_STREAM,0);
        if(s_fd==-1)
        {
                perror("socket");
                exit(-1);
        }

        s_addr.sin_family=AF_INET;
        s_addr.sin_port=htons(atoi(argv[2]));
        inet_aton(argv[1],&s_addr.sin_addr);
        //2.bind
        bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
        //3.listen
        listen(s_fd,10);
        //4.accept

        //      int accept4(int sockfd, struct sockaddr *addr,socklen_t *addrlen, int flags);
        int addrlen=sizeof(struct sockaddr_in);

        while(1)
        {
                c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&addrlen);
                if(c_fd==-1)
                {
                        perror("accept");
                }

                printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
                //5.read

                mark++;

                if(fork()==0)
                {
                        if(fork()==0)
                        {
                                while(1)
                                {
                                        sprintf(writeBuf,"Welcome NO.%d client:",mark);
                                        write(c_fd,writeBuf,strlen(writeBuf));
                                        sleep(3);
                                }
                        }
                //6.write
                //      ssize_t write(int fd, const void *buf, size_t count);
                		while(1)
                        {
                                //      ssize_t read(int fd, void *buf, size_t count);
                                memset(&readBuf,0,sizeof(readBuf));
                                n_read=read(c_fd,readBuf,128);
                                if(n_read==-1)
                                {
                                        perror("read");
                                }
                                else
                                {
                                        printf("get message:%d,%s\n",n_read,readBuf);
                                }
                        }
                        break;
                }
        }
        return 0;
}

client2同client1

运行结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Salute to 老陈!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值