socket详解

套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口。

套接字的接口介绍:

1、创建套接字

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

       #include <sys/socket.h>

 

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

domain:地址域--选择使用哪种地址结构,iPV4 --AF_INET

type:套接字类型 数据报套接字 --SOCK_DGRAM  --提供数据报传输服务/流式套接字 --SOCK_STREAM--提供字节流传输服务

protocol :协议类型--决定了提供什么样的传输服务

0--套接字类型中的默认协议--/SOCK_STREAM -默认是tcp/SOCK_DGRAM --默认是udp

IPPROTO_TCP -tcp协议    IPPROTO_UDP  -udp协议

返回值:返回一个文件描述符作为操作句柄;失败返回-1;

 

 数据报传输服务:无连接的,不可靠的,有最大长度限制的消息传输服务

字节流传输服务:有序的、可靠的、双向的、基于连接的传输服务

 

protocol:传输所使用的协议类型  0-不同套接字类型的默认协议

数据报套接字:默认协议是UDP / 流式套接字:默认协议是TCP

IPPROTO_TCP:TCP协议    IPPROTO_UDP :UDP协议

返回值:套接字操作句柄--文件描述符

 

2、为套接字绑定地址信息

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

sockfd :套接字操作句柄,

 addr: 地址信息结构首地址--用于描述内核中socket中所使用的的信息

addrlen:地址信息结构长度--bind接口是同一的地址绑定接口,但是地址结构确是多种多样,长度不一

 

struct sockaddr_in addr;

addr.sin_family = ...  sin_port = ... sin_addr = ...

bind(sockfd,(struct sockaddr*) & addr ,len)

 

addrlen:地址信息的长度

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

 

网络中的每条数据都要描述 源端口 源IP地址 目的端口 目的IP地址 协议  (五元组)

3、发送数据:将数据拷贝到内核态中的socket发送缓冲区中(操作系统会取出数据进行层层协议封装,将其发送出去)

    ssize_t sendto(int sockfd,char * data ,int data_len ,int flag ,struct_sockaddr * dest_addr,socklen_t addr_len);

socketfs:指定内核中的socket结构体,绑定的地址信息作为数据中的源信息对数据进行描述。

data:要发送的数据首地址

data_len:要发送的数据长度

flag:选项参数--通常默认为0--表示阻塞发送数据(若发送缓冲区中数据已满,则需要等待)

dest_addr:目的端的地址信息首地址--用于告诉socket说这个数据要发送给谁

addr_len 地址信息的长度

     返回值:成功返回实际发送的数据长度,失败返回-1

 

4、接收数据:进程通过操作句柄从内核的socket接收缓冲区中取出已经接收到的数据(操作系统已经进行了传输层以下的数据分用)

        int recvfrom(int socket,char *buf int buf_len ,int flag,struct sockaddr * peer_addr ,socklen_t * addr_len);

 

socket:指定内核中的socket结构体---从哪个socket的接收缓冲区中取出数据

buf :用户态缓冲区,用于存放从内核拷贝出来的数据

buflen :我们想要获取的数据长度,这个长度不能大于buf的长度

flag:默认为0--阻塞接收-缓冲区中没有数据的时候走的阻塞等待

peer_addr:地址缓冲区首地址--用于获取发送这个数据的源端地址信息--告诉我们这个数据是谁发送的

addr_len:输入输出型参数--用于指定想要获取多长的地址信息;并且用于返回实际获取的地址长度

返回值:成功返回实际接收到的数据长度;失败返回-1;

 

5、关闭套接字

 

int close(int fd)

 

字节序转换接口

 

uint32_t htonl(uint32_t hostlong); //将4个字节的整数转换为网络字节序整数

uint16_t htons(uint16_t hostshort); //将2个字节的整数转换为网路字节序整数

uint32_t ntohl(uint32_t netlong);   //将4字节的网络字节序整数转换为主机字节序的整数

uint16_t ntohs(uint16_t netshort);  //将2个字节的网络字节序整数转换成为主机字节序整数

 

int inet_pton(int af,const char * src ,void *dst);  将一个字符串IP地址转换为网络字节序整数IP地址

af:地址域--说明这是一个什么地址结构,AF_INET

src:字符串的IP地址

dst:转换后的网络字节序的整数IP地址

                                    

const char * inet_ntop(int af,const void*src,char *dst,socket_t size);将一个网络字节序整数IP地址转换为字符串IP地址

af:地址域--说明这是一个什么地址结构 AF_INET

src:网络字节序的整数IP地址

dst:缓冲区,用于接收转换后的字符串IP地址 

size:缓冲区的长度

代码实现:

封装一个tcpsock类

#include <cstdio>
#include <string>
#include <vector>
#include <unistd.h>
#include <fcntl.h>//设置阻塞接口
#include <netinet/in.h>//地址结构体信息
#include <arpa/inet.h>//字节序转换接口
#include <sys/socket.h>//套接字接口
#define MAXCONNECT 10
#define CHECK_RET(q) if((q)==false){return -1;}

class TcpSocket
{
public:
	TcpSocket() :_sockfd(-1)
	{}
	int GetFd()
	{
		return _sockfd;
	}
	void SetFd(int fd)
	{
		_sockfd = fd;
	}
	void SetNonBlock()
	{
		//fcntl获取文件描述符的属性/设置文件描述符的属性
		int flag = fcntl(_sockfd, F_GETFL, 0);//获取原来的属性
		fcntl(_sockfd, F_SETFL, flag | O_NONBLOCK);//在原有属性上增加非阻塞属性
	}
	bool Socket()
	{
		//socket(地址域,套接字类型,协议类型
		_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (_sockfd < 0)
		{
			perror("socket error");
			return false;
		}
		return true;
	}
	void Addr(struct sockaddr_in *addr, const std::string ip, uint16_t port)
	{
		addr->sin_family = AF_INET;
		addr->sin_port = htons(port);
		inet_pton(AF_INET, ip.c_str(), &addr->sin_addr.s_addr);
	}
	//绑定地址信息
	bool Bind(const std::string & ip, uint16_t port)
	{
		struct  sockaddr_in addr;
		Addr(&addr, ip, port);
		socklen_t len = sizeof(struct sockaddr_in);
		int ret = bind(_sockfd, (struct sockaddr *)&addr, len);
		if (ret < 0)
		{
			perror("bind error");
			return false;
		}
		return true;
	}
	//开始监听
	bool Listen(int max_con = MAXCONNECT)
	{
		//listen(服务端监听套接字描述符,同时最大连接数)
		int ret = listen(_sockfd, max_con);
		if (ret < 0)
		{
			perror("listen error");
			return false;
		}
		return true;
	}
	//客户端发起连接请求
	bool Connect(const std::string ip, uint16_t port)
	{
		struct sockaddr_in addr;
		Addr(&addr, ip, port);
		//connect(客户端套接字描述符,服务端地址信息,地址信息长度)
		socklen_t len = sizeof(struct sockaddr_in);
		int ret = connect(_sockfd, (struct sockaddr *)&addr, len);
		if (ret < 0)
		{
			perror("connect error");
			return false;
		}
		return true;
	}
	//获取新连接
	bool Accept(TcpSocket *sock, std::string *ip=NULL, uint16_t port=NULL)
	{
		//accept(监听套接字描述符,客户端地址信息,地址信息长度)返回新的操作句柄
		struct sockaddr_in cliaddr;
		socklen_t len = sizeof(struct sockaddr_in);
		int new_fd = accept(_sockfd, (struct sockaddr *)&cliaddr, &len);
		if(new_fd<0)
		{
			perror("accept error");
			return false;
		}
		sock->_sockfd = new_fd;
		if (ip != NULL)
		{
			*ip = inet_ntoa(cliaddr.sin_addr);
		}
		if (port != NULL)
		{
			*port = ntohs(cliaddr.sin_port);
		}
		return true;
	}
	//接收数据
	bool Recv(std::string *buf)
	{
		//recv(描述符,缓冲区,想要的数据长度,选项参数-0默认阻塞)
		while (1)
		{
			char tmp[5] = { 0 };
			size_t ret = recv(_sockfd, tmp, 5, 0);
			if (ret < 0)
			{
				if (errno == EAGAIN)
				{
					//表示非阻塞下,缓冲区中没有数据
					return true;
				}
			}
			else if (ret == 0)
			{
				//连接已断开
				printf("connect break\n");
				return false;
			}
			*buf += tmp;
		}
		return true;
	}
	//发送数据
	bool Send(const std::string & data)
	{
		//send(描述符,数据,数据长度,选项数据
		size_t ret = send(_sockfd, data, sizeof(data), 0);
		if (ret < 0)
		{
			perror("send error");
			return false;
		}
		return true;
	}

	//关闭套接字
	bool Close()
	{
		close(_sockfd);
		_sockfd = -1;
		return true;
	}
private:
	int _sockfd;
};

tcpsrv

#include <iostream>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#include "tcpsocket.hpp"

void sigcb(int signo) 
{
    //waitpid() > 0 表示有子进程退出--若返回值<=0则表示没有子进程退出了
    //WNOHANG--非阻塞操作
    //SIGCHLD信号是一个非可靠信号有可能丢失事件-因此循环处理到把已经退出的完全处理完毕
    while(waitpid(-1, NULL, WNOHANG) > 0);
}
int main(int argc, char *argv[])
{
    if (argc != 3) {
        printf("em: ./tcp_srv host_ip host_port\n");
        return -1;
    }
    //对SIGCHLD信号--进程 退出信号--自定义处理,等到子进程退出的时候处理一下就可以
    //这样就避免了父进程一直等待的情况
    signal(SIGCHLD, sigcb);//自定义信号处理函数,信号和中断函数的功能类似,,,,
    std::string ip = argv[1];
    uint16_t port = atoi(argv[2]);

    TcpSocket lst_sock;
    CHECK_RET(lst_sock.Socket()); //创建套接字
    CHECK_RET(lst_sock.Bind(ip, port));//绑定地址信息
    CHECK_RET(lst_sock.Listen());//开始监听
    while(1) {
        TcpSocket newsock;
        std::string cli_ip;
        uint16_t cli_port;
        bool ret = lst_sock.Accept(&newsock,&cli_ip, &cli_port);//获取新连接,得到对端地址和端口信息
        if (ret == false) {
            continue;//服务端不会因为一次获取的失败而退出,而是继续重新获取下一个
        }
        printf("new conn:[%s:%d]\n", cli_ip.c_str(), cli_port);
        pid_t pid = fork();
        if (pid == 0) {
            //每个子进程负责与一个客户端进行循环通信
            while(1) {
                //------------------------------------------------------------------
                std::string buf;
                ret = newsock.Recv(&buf);//通过新连接接收数据
                if (ret == false) {newsock.Close(); continue;}
                printf("client say: %s\n", buf.c_str());
                //-----------------------------------------------------------------
                printf("server say:");//没有带换行,因此不会自动刷新标准输出缓冲区
                fflush(stdout); // 手动刷新标准输出缓冲区-将数据打印出来
                buf.clear();  //清空buf这个string对象的内容
                std::cin >> buf;
                ret = newsock.Send(buf);//通过新连接发送数据
                if (ret == false) {newsock.Close(); continue;}
            }
            newsock.Close();
            exit(0);
        }
        //注意:新的套接字创建在创建子进程之前,因此父子进程各自独有一份
        //父进程并不与客户端进行通信,因此直接关闭新的套接字-关闭的是自己的不影响子进程
        newsock.Close();
    }//循环处理就是要用while,信号具有像中断函数一样的功能
    lst_sock.Close();
    return 0;
}

tcpcli

 
#include <iostream>
#include <stdlib.h>
#include "tcpsocket.hpp"


int main(int argc, char *argv[])
{
    if (argc != 3) {
        printf("em: ./tcp_cli srv_ip srv_port\n");
        return -1;
    }
    std::string ip = argv[1];
    uint16_t port = atoi(argv[2]);

    TcpSocket cli_sock;
    CHECK_RET(cli_sock.Socket());//创建套接字
    CHECK_RET(cli_sock.Connect(ip, port));//向服务端发起连接
    while(1) {
        std::cout << "client say:";
        std::string buf;
        std::cin >> buf;
        CHECK_RET(cli_sock.Send(buf));//当前客户端收发数据出错直接退出--

        buf.clear();
        CHECK_RET(cli_sock.Recv(&buf));//正常情况下应该关闭套接字然后重新建立连接
        std::cout << "server say: " << buf << std::endl;
    }
    cli_sock.Close();
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值