socket套接字

5 篇文章 0 订阅
4 篇文章 0 订阅

一、套接字
基本特点:socket是一种接口技术,被抽象成一个文件操作,可以让进程之间通信,也可以让不同计算机的进程通信(网络)。

int socket(int domain, int type, int protocol);
功能:创建套接字
domain:
    AF_UNIX/AF_LOCAL 本地通信,进程间通信
    AF_INET 基于IPv4地址通信
    AF_INET6 基于IPv6地址通信
type:
    SOCK_STREAM 数据流协议 
    SOCK_DGRAM 数据报协议
protocol:特殊通信协议,一般不用,写0即可。
返回值:成功返回套接字描述符,失败返回-1。

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:绑定socket和地址(文件路径或网络地址)
sockfd:socket描述符
addr:地址结构体
    // 基本地址类型
    struct sockaddr {
        sa_family_t sa_family;
        char        sa_data[14];
    }
    // 本地地址类型 #include <sys/un.h>
    struct sockaddr_un{
        sa_family_t sun_family; // 地址簇
        char sun_path[108]; // socket文件路径
    };
    // 网络地址类型 #include <netinet/in.h>
    struct sockaddr_in{
        sa_family_t sin_family; // 地址簇
        in_port_t sin_port;     // 端口号
        struct in_addr sin_addr;// IP地址
    };
    struct in_addr{
        in_addr_t s_addr;
    }
addrlen:地址结构体的字节数
返回值:成功返回0,失败返回-1。

int listen(int sockfd, int backlog);
功能:监听socket,数据流通信时使用
sockfd:socket描述符
backlog:排队数量
返回值:成功返回0,失败返回-1。

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:连接socket
sockfd:socket描述符
addr:目标地址
addrlen:地址的字节数
返回值:成功返回0,失败返回-1。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:等待连接
sockfd:socket描述符
addr:获取连接者的地址
addrlen:既输入双输出
    既告诉accept函数当前系统地址结构体的字节数,同时也获取发者地址结构体的字节数
返回值:返回一个建议连接后的socket描述符

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:从socket读取数据,数据流通信时使用
sockfd:建立连接后的socket描述符
buf:存储数据的缓冲区地址
len:缓冲区的字节数
flags: 一般写0即可
    MSG_OOB 优先接收外带数据
    MSG_DONTWAIT 不阻塞
返回值:接收到的字节数,-1出现错误,0连接断开。

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:向socket发送数据,数据流通信时使用
sockfd:建立连接后的socket描述符
buf:待发送的数据首地址
len:要发送的字节数
flags:一般写0即可
    MSG_OOB 优先紧急数据
    MSG_DONTWAIT 不阻塞
返回值:成功发送的字节数,出错返回-1。

int close(int fd);
功能:关闭socket

TCP网络通信编程模型:
计算机S 计算机C
创建套接字 创建套接字
准备通信地址(自己的) 准备通信地址(计算机S,与C在同一局域网或S是公用IP)
绑定套接字和地址 连接计算S
监听 …
等待连接 …
接收/发送数据 发送/接收数据
关闭套接字 关闭套接字

多路复用:
由于为了实现服务器的并发(同时服务多个客户端),需要为每个客户端创建一个进程来为它服务,但创建进程、销毁进程非常浪费时间与资源,这个问题有两种解决方案:
1、多路复用:一个进程监控所有的客户端fd+服务端fd。
2、多线程:为每个客户端创建一个线程进行服务。

由于有些读写函数需要以阻塞状态调用,同时可能有多个文件描述需要同时读写,多路复用就是同时监控多个文件的描述符。

int select(int nfds, fd_set *readfds, fd_set *writefds,
              fd_set *exceptfds, struct timeval *timeout);
功能:监控若干个文件描述符
nfds:最大的文件描述符加1
readfds:需要监控读操作的文件描述符集合,既输入又是输出。
writefds:需要监控写操作的文件描述符集合,既输入又是输出。
exceptfds:需要监控异常的文件描述符时间,既输入又是输出。
timeout:倒计时时间
struct timeval {
    long    tv_sec;         /* seconds */
    long    tv_usec;        /* microseconds */
};

void FD_CLR(int fd, fd_set *set);
功能:从集合中删除fd

int  FD_ISSET(int fd, fd_set *set);
功能:测试集合是否有fd存在

void FD_SET(int fd, fd_set *set);
功能:向集合中添加文件描述符

void FD_ZERO(fd_set *set);
功能:清空文件描述符

int epoll_create(int size);
功能:创建epoll对象
size:epoll对象的大小,因为内核会自动调整epoll对象的大小,因此该参数目前已经作废。
返回值:epoll对象的描述符

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:操作epoll对象,可以添加、删除文件描述符
epfd:epoll对象描述符
op:
    EPOLL_CTL_ADD 添加文件描述符
    EPOLL_CTL_MOD 修改文件描述符的监控事件
    EPOLL_CTL_DEL 删除文件描述符
fd:
    要操作的文件描述符
event:
    struct epoll_event {
        uint32_t     events;      /* Epoll events */
            EPOLLIN 读事件
            EPOLLOUT 写事件
        epoll_data_t data;        /* User data variable */
    };

int epoll_wait(int epfd, struct epoll_event *events,
                  int maxevents, int timeout);
功能:监控epoll对象中的文件描述符
epfd:epoll对象
events:保存发生事件的文件描述符集合
maxevents:最多保存多少个文件描述符
timeout:等待时间,1000/1秒,-1则一直等待。
返回值:事件发生手文件描述符个数

常考面试题:select与epoll的区别。

本地字节序与网络字节序转换:
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字节网络字节序数据转换成本地字节序

IPv4地址转换:
int inet_aton(const char *cp, struct in_addr *inp);
功能:把点十进制的IP地址转换成 struct sockaddr_in中的sin_addr成员类型。

in_addr_t inet_addr(const char *cp);
功能:把点十进制的IP地址转换成 struct sockaddr_in中的sin_addr.s_addr,通过返回值返回。

in_addr_t inet_network(const char *cp);
功能:把点十进制的IP地址转换成,转换成本地字节序的二进制。

char *inet_ntoa(struct in_addr in);
功能:struct sockaddr_in中的sin_addr成员转换成点分十进制的ip地址。

struct in_addr inet_makeaddr(int net, int host);
功能:把网络地址与主机地址合并成IP地址

in_addr_t inet_lnaof(struct in_addr in);
功能:计算IP地址的主机地址
    ip & ~掩码

in_addr_t inet_netof(struct in_addr in);
功能:计算IP地址的网络地址
    ip & 掩码

UDP网络通信:
UDP是无连接的通信协议,准备好地址之后可以直接发送或接收数据。

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
功能:发送数据
sockfd:发送者的socket描述符
buf:待发送的数据
len:数据的长度
flags:一般写0即可。
dest_addr:目标地址
addrlen:地址的长度
返回值:成功发送的数据的字节数

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
功能:接收数据
sockfd:接收者的socket描述符
buf:存储数据的缓冲区
len:缓冲区的长度
flags:一般写0即可
src_addr:存储发送者的地址
addrlen:既是输入(当前系统地址的字节数)也是输出(获取发送者地址的字节数),存储发送者地址的长度。
返回值:成功接收到的数据的字节数。

UDP网络通信编程模型:
计算机S 计算机C
创建socket对象 创建socket对象
准备通信地址 准备通信地址(S的地址)
绑定socket对象和地址
接收/返回数据 发送/接收数据
关闭socket对象 关闭socket对象

UDP广播通信:
广播就是发送者发出一条数据,路由器把这条数据向局域网中的所有计算转发一次。
如何广播:就是向一个特殊地址发送数据,同时还要设置sock对象的属性。
广播地址就是除了网络地址其它位全为1。
192.168.0.113
255.255.255.0
192.168.0 网络地址
113 主机地址
192.168.0.255 广播地址

int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
功能:设置socket对象的属性
sockfd:socket对象的描述符
level:要设置的协议层
SOL_SOCKET
SO_BROADCAST 广播
IPPROTO_IP
IP_MULTICAST_TTL 多播
IPPROTO_TCP
optname:要设置的选项名
optval:存储要修改的值的缓冲区
optlen:缓冲区的长度

int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);

TCP服务端代码

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

typedef struct sockaddr* SP;

int main(int argc,const char* argv[])
{
	// 创建socket
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(0 > sockfd)
	{
		perror("socket");
		return EXIT_FAILURE;
	}

	// 准备地址
	struct sockaddr_in addr = {};
	addr.sin_family = AF_INET;
	addr.sin_port = htons(8866);
	addr.sin_addr.s_addr = INADDR_ANY;
	socklen_t addrlen = sizeof(addr);

	// 绑定
	if(bind(sockfd,(SP)&addr,addrlen))
	{
		perror("bind");
		return EXIT_FAILURE;
	}

	// 监听
	if(listen(sockfd,5))
	{
		perror("listen");
		return EXIT_FAILURE;
	}

	// 等待连接
	int clifd = accept(sockfd,(SP)&addr,&addrlen);
	if(0 > clifd)
	{
		perror("accept");
		return EXIT_FAILURE;
	}

	char buf[4096] = {};
	size_t buf_size = sizeof(buf);

	for(;;)
	{
		// 接收/返回数据
		size_t ret_size = read(clifd,buf,sizeof(buf));
		if(0 == strcmp("quit",buf))
		{
			printf("通信结束!\n");
			break;
		}	
		if(0 == ret_size)
		{
			printf("连接断开!\n");
			break;
		}
		printf("recv:[%s] bits:%d\n",buf,ret_size);
		strcat(buf,":return");
		write(clifd,buf,strlen(buf)+1);
	}

	// 关闭socket
	close(clifd);
}

TCP客户端代码`

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>

int main(int argc, char const *argv[])
{
    // 初始化网络库
    WSADATA wsaData;
    if(WSAStartup(MAKEWORD(2,2),&wsaData))
    {
        perror("WSAStartup");
        return EXIT_FAILURE;
    }

    // 创建socket
    int clifd = socket(AF_INET,SOCK_STREAM,0);
    if(0 > clifd)
    {
        perror("socket");
        return EXIT_FAILURE;
    }

    // 准备通信地址(由服务端决定)
    struct sockaddr_in addr = {};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9988);
    addr.sin_addr.s_addr = inet_addr("47.97.229.46");

    // 连接
    if(connect(clifd,(struct sockaddr*)&addr,sizeof(addr)))
    {
        perror("connect");
        return EXIT_FAILURE;
    }

    char buf[4096] = {};
    size_t buf_size = sizeof(buf);

    for(;;)
    {
        printf(">>>");
        gets(buf);
        send(clifd,buf,strlen(buf),0);
        if(0 == strcmp("quit",buf))
        {
            printf("通信结束!\n");
            break;
        }
        size_t ret_size = recv(clifd,buf,buf_size,0);
        printf("recv:[%s],bits:%d",buf,ret_size);
    }

    // 关闭socket
    closesocket(clifd);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值