Linux学习之旅(27)----套接字编程基础知识

什么是套接字(Socket)?

TCP用主机的IP地址加上主机上的端口号作为TCP连接的端点,这种端点就叫做套接字或插口。这里可以将套接字理解为一个“中间人”的角色,在TCP/IP通讯模型中,两台主机是不能直接进行通信的,需要通讯必须经过套接字,通讯双方将需要通讯的信息交给各自的套接字,然后由这两个套接字进行通信,套接字是支持网络通讯的基本单元。套接字包含网络通信的基本信息:(1)源主机IP地址、(2)源主机端口号、(3)目标主机IP地址、(4)目标主机端口号、(5)连接使用的协议。(通过IP地址和端口号可以唯一的确定一台的主机上的一个应用)

常用的TCP/IP协议的3中套接字的类型:

(1)流式套接字(SOCK_STREAM)

流式套接字用于提供面向连接的、可靠的数据传输服务。该服务将保证数据能够实现误差错、无重复发送,并按顺序接收。流式套接字采用的协议是TCP(面向字节流)。

(2)数据报套接字(SOCK_DGRAM)

数据报套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数量重复,且无法保证顺序的接收到数据。数据报套接字使用的协议是UDP(面向报文)。

(3)原始套接字(SOCK_RAW)

原始套接字允许对较低的层次的协议直接访问,比如IP,ICMP协议,常用于检验新的协议实现,或者访问现有服务配置的新设备。

原始套接字和标准套接字(流式、数据报)的区别在于:原始套接字可以读写内存中没有处理的IP数据报,而标准套接字只能 读取TCP/UDP协议的数据,因此如果需要访问其他协议发送的数据就必须使用原始套接字。


这里线介绍两组函数,之后可以用到。

IP地址转化函数

#include <arpa/inet.h>
int inet_pton(int af,const char *src,void* dst);
const char *inet_ntop(int af,const void *src,char *dst,socklen_t size);

支持IPV4和IPV6,函数可重入。

inet_pton:将“点分十进制”转化为“二进制整数”。将转化后的内容存放在dst中。dst的类型可以时ipv4或ipv6类型。

inet_ntop:将“二进制整数”转化为“点分十进制”。将转化的内容存放在dst中。

sockaddr数据结构

struct socket很多网络编程函数诞生于早于IPV4协议,现在已经很少使用了,但为了向前兼容,现在sockaddr退化成了(void*)的作用,传递一个地址给函数,至于这个函数是sockaddr_in还是sockaddr_in6,由地址族确定,然后函数内部在强制类型转化位所需的地址类型。

#include <netinet/in.h>

struct sockaddr
{
    sa_family_t sa_family; //地址族
    char        sa_data[14];//14个字节,包含套接字中目标地址和端口信息
};

struct sockaddr_in
{
    sa_family_t    sin_family;  //地址族 AF_INET(IPV4)
    uint16_t       sin_port;    //端口号
    struct in_addr sin_addr;    //IP地址
    char           sin_zero[8]  //8字节的填充
};

struct in_addr
{
    uint32_t   s_addr;
};

TCP网络编程架构

TCP网络编程有两种模式,一种是服务器模式,另一种是客户端模式。服务器模式。服务器模式创建服务程序等待客户端程序连接,接收到用户的连接请求后,根据用户的请求进行处理;客户端模式则根据目的服务器的地址和端口进行连接,向服务器发送请求并对服务器的响应进行数据处理。

网络套接字函数

1、创建网络端口函数socket()

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain,int type,int protocol);

函数功能:打开一个网络通讯端口。

参数说明:

domain:用于设置网络通信的域。

常见的网络通讯域:

AF_INET:使用TCP或UDP传输,IPV4。

AF_INET6:使用TCP或UDP传输,IPV6。

AF_UNIX:本地协议,当客户端和服务器在同一台机器上的时候使用。

type:用于设置套接字的通讯的类型。

SOCK_STREAM:流式套接字

SOCK_DGRAM:数据报套接字

SOCK_RAW:原始套接字

SOCK_RDW:提供可靠的数据报文,不过可能数据会有乱序。

SOCK_PACKET:这是一个专用类型,不能再通用程序中使用。

SOCK_SEQPACKET:序列化包,提供一个序列化的、可靠的、双向的基于连接的数据传输通道,数据长度定长。每次调用读系统调用时需要将数据全部读出。

protocl:0,采用默认协议。

返回值:成功返回新的文件描述符,失败返回-1.设置errno。

常见的错误原因:

含义
EACCES没有权限建立指定domain的type的socket
EAFNOSUPPORT不支持所给的地址类型
EINVAL不支持此协议或者协议不可用
EMFILE进程文件表溢出
ENFILE

已经达到系统允许打开的文件数量,打开文件过多

ENOBUFS/ENOMEM内存不足。
EPROTONOSUPPORT指定的协议在type在damain中不存在

2、绑定一个地址端口对bind()

在建立套接字描述符成功后,需要对套接字进行地址和端口的绑定,才能进行数据的接收和发送的操作。

#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd,const struct sockadder *my_addr,socklen_t addrlen);

函数功能:

将套接字和IP地址、端口绑定在一起。因为服务的地址和端口号是固定的,所以需要绑定,但对于客户端则不需要。

参数说明:

sockfd:socket()函数创建的文件描述符。

my_addr:是一个指向结构为socksddr的参数的指针,sockaddr中包含了IP地址和端口的信息。

addlen:my_addr的结构体的长度。

返回值:成功返回0,失败返回-1,并设置errno。

3、监听本地端口listen()

#include <sys/socket.h>
int listen(int sockfd,int backlog);

函数功能:

初始化服务器可连接队列,并监听端口。

参数说明:

sockfd:socket的文件描述符。

backlog:已建立连接队列和等待建立连接队列的长度的总和。

返回值:成功返回0,失败返回-1,并设置errno。

系统默认的backlog存放在 /proc/sys/net/ipv4/tcp_max_syn_backlog中,可以通过cat命令查看。

如果指定的值小于系统规定的值,那么backlog的值为指定的值,就为系统规定的值。

4、接收一个网络请求accept()

#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);

函数功能:

接收一个客户端的连接。如果服务调用accept()时还没有客户端的连接请求,就阻塞等待,直到客户端连接上来。

参数说明:

sockfd:socket文件描述符。

addr:传出参数,返回连接客户端的地址信息,包括IP地址和端口号。

addrlen:传入传出参数(值-结果),传入sizeof(addr)大小,函数返回真正的接收到的地址结构体大小。

返回值:成功返回一个新的socket描述符(用于和客户端通信)失败返回-1,并设置errno

accept函数时处理客户端连接的,但accept一次只能处理一个客户端,如果同时有很多个客户端同时连接,来不及处理的客户端就会处理待处理队列中,等待建立连接。如果已建立连接队列和待处理的队列的总和超过backlog,服务器会直接发送一个RST复位包。

程序:简单的服务器模型

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

int main()
{
    int listenSock;
    int clientSock;
    char ipStr[128];
    listenSock=socket(AF_INET,SOCK_STREAM,0);    //创建一个使用网络IPV4,运输TCP的套接字
    struct sockaddr_in serAddr;                  //存放服务器监听套接字IP地址和端口号
    struct sockaddr_in cliAddr;                  //存放服务中负责和客户端通信的套接字的IP地址和端口号
    bzero(&(serAddr),sizeof(serAddr));           //将结构体数据清零
    bzero(&(cliAddr),sizeof(cliAddr));
    serAddr.sin_family=AF_INET;                  //设置协议,需要和套接字中的相同
    serAddr.sin_port=htons(5500);                //设置端口号!(1-1024)
    serAddr.sin_addr.s_addr=htonl(INADDR_ANY);   //IP为本地的IP。0.0.0.0 监听本机的所以IP端口
    bind(listenSock,(struct sockaddr *)(&serAddr),sizeof(serAddr));//绑定端口信息
    listen(listenSock,20); //监听端口,backlog=20
    printf("SERVER LISTENING...\n");
    while(1)
    {
        int len=sizeof(cliAddr);
        clientSock=accept(listenSock,(struct sockaddr *)(&cliAddr),&len);//处理连接
        printf("Client IP:%s",inet_ntop(AF_INET,&(cliAddr.sin_addr.s_addr),ipStr,sizeof(ipStr)));
        printf("ipstr %s\n",ipStr);
        printf("client prot:%d\n",ntohs(cliAddr.sin_port));
        close(clientSock);
    }
    close(listenSock);
    return 0;
}

还没编写客户端可以利用nc命令来测:

nc -nv ip地址   端口号

5、连接目标网络服务器connect()

客户端建立连接之后,不需要进行地址绑定,就可以直接连接服务器。连接服务器的函数为connent(),此函数连接指定的服务器。

#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd,struct sockaddr *my_addr,int addrlen);

客户端需要调用connect()连接服务器,connect和bind的参数形式一致,区别在于bind的参数自己的地址,而connent的参数是对方的地址。成功返回0.失败返回-1.

程序二:简单客户端的程序

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#define IPSIZE 128
int main()
{
	int cliSock=socket(AF_INET,SOCK_STREAM,0);
	struct sockaddr_in serAddr;
	bzero(&(serAddr),sizeof(serAddr));
	serAddr.sin_family=AF_INET;
	serAddr.sin_port=htons(5500);
	char strSerIP[IPSIZE];
	scanf("%s",strSerIP);
	struct in_addr serIP;
	inet_pton(AF_INET,strSerIP,(void*)(&serIP));
	serAddr.sin_addr.s_addr=serIP.s_addr;
	if(connect(cliSock,(struct sockaddr*)(&serAddr),sizeof(serAddr))==-1)
	{
		printf("连接失败!\n");
		exit(1);
	}
	printf("连接成功!\n");
	close(cliSock);
	return 0;
}

因为网卡时单网卡,所以每次的IP地址都是一样的,但端口号不相同。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值