Linux下socket编程基础1-初探
Linux下socket套接字基础
int socket(int __domain, int __type, int __protocol);
- domain:是协议域,这个参数决定了socket的地址类型,常用的是AF_INET(ipv4地址)和16位端口号组合
- type:是指定socket类型,常用的socket类型,SOCK_STREAM(基于TCP),SOCK_DGRAM(基于UDP)
- protocol: 指定协议,常用IPPROTO_TCP、IPPTOTO_UDP等
调用socket()返回的描述字存在于协议族空间中,但是没有一个具体的地址,所以需要调用下面的bind()函数设置具体地址,否则当调用connect()、listen()时这个端口系统会随机分配。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd:就是socket的描述字,socket()的返回值
- *addr :
一个指向sockaddr结构体的指针,指向要绑定给sockfd的协议地址,这个结构体随着地址结构的改变而调用不同的结构体
- ipv4:
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
- ipv6:
struct sockaddr_in6 {
sa_family_t sin6_family; /* AF_INET6 */
in_port_t sin6_port; /* port number */
uint32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */
};
struct in6_addr {
unsigned char s6_addr[16]; /* IPv6 address */
};
- addlen: 对应的是地址的长度
int listen(int sockfd, int backlog);
服务端通过listen()将socket转变为被动类型,等待客户的连接请求
- sockfd: 第一个参数即为要监听的socket描述字.
- backlog: 第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
客户端通过调用connect()来建立于服务器的连接
- sockfd: 第一个参数是客户端的socket描述字
- *addr : 第二个参数是服务器的socket地址
- addrlen :第三个参数是服务器socket地址的长度
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就向TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。
- sockfd : 服务器socket描述字
- *addr : 返回客户端的socket地址
- *addrlen : 客户端的socket地址长度
- 返回值: 已连接的socket描述字
注意:服务器通常只需要创建一个socket套接字,这个套接字在服务器生命周期内一直存在。而客户端连接成功服务端后,内核会创建一个新的已连接的socket套接字,当服务器对此客户端完成服务之后,这个socket描述字就会被关闭。
recvmsg()和sendmsg()函数(最通用的I/O函数)
用这两个函数可替代
- read()/write()
- recv()/send()
- readv()/writev()
- recvfrom()/sendto()
#include <sys/socket.h>
#include <sys/uio.h>
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
struct msghdr {
void *msg_name; // protocol address
socklen_t msg_namelen; // size of protocol address
struct iovec *msg_iov; // scatter/gather array
int msg_iovlen; // elements in msg_iov
void *msg_control; // ancillary data (cmsghdr struct)
socklen_t msg_controllen; // length of ancillary data
int msg_flags; // flags returned by recvmsg()
};
struct iovec {//依赖于头文件sys/uio.h
void *iov_base; /* starting address of buffer */
size_t iov_len; /* size of buffer */
}
-
msg_name和msg_namelen
这两个成员用于套接字未连接的场合(如未连接 UDP 套接字)。
如果用于TCP的话,msg_name置为空指针,namelen置为0。 -
msg_iov和msg_iovlen
这两个成员指定输入输出缓冲区数组以及数组的多少,结合struct iovec来看,msg_iov是个二维数组 -
msg_control和msg_controllen
这两个成员指定可选的辅助数据的位置和大小 -
msg_flags
对于sendmsg,这个参数可以忽略掉。
对于recvmsg,它本身的参数flags会被复制到msg_flags成员,由内核使用这个值驱动接收处理过程。
recvmsg 返回的 7 个标志如下:
- MSG_BCAST:本标志随 BSD/OS 引入,相对较新。它的返回条件是本数据包作为链路层广播收取或者其目的 IP 地址是一个广播地址。与 IP_RECVD-STADDR 套接字选项相比,本标志是用于判定一个 UPD 数据包是否发往某个广播地址的更好方法。
- MSG_MCAST:本标志随 BSD/OS 引入,相对较新。它的返回条件是本数据报作为链路层多播收取。
- MSG_TRUNC:本标志的返回条件是本数据报被截断,也就是说,内核预备返回的数据超过进程事先分配的空间(所有 iov_len 成员之和)。
- MSG_CTRUNC:本标志的返回条件是本数据报的辅助数据被截断,也就是说,内核预备返回的辅助数据超过进程事先分配的空间(msg_controllen)。
- MSG_EOR:本标志的返回条件是返回数据结束一个逻辑记录。TCP 不使用本标志,因为它是一个字节流协议。
- MSG_OOB:本标志绝不为 TCP 带外数据返回。它用于其他协议族(如 OSI 协议族)。
- MSG_NOTIFICATION:本标志由 SCTP 接收者返回,指示读入的消息是一个事先通知,而不是数据消息。
示例代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <string>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <netinet/in.h>
using namespace std;
void My_server(void){
//初始化发送数据缓冲区
char buf[128]={0};
struct iovec buffer={
.iov_base = buf,
.iov_len = sizeof(buf)
};
struct msghdr Mymsg={
.msg_name = nullptr,
.msg_namelen =0,
.msg_iov = &buffer,
.msg_iovlen = 1,
.msg_control = nullptr,
.msg_controllen = 0,
.msg_flags = 0
};
//创建套接字
int Mysocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
//将套接字和IP、端口绑定
struct sockaddr_in Server_Addr;
memset(&Server_Addr,0,sizeof(Server_Addr));
Server_Addr.sin_family=AF_INET; //设置使用ipv4地址
Server_Addr.sin_addr.s_addr=inet_addr("127.0.0.1");//设置具体的ip地址(本地环回)
Server_Addr.sin_port=htons(12345); //端口
bind(Mysocket,(struct sockaddr *)&Server_Addr,sizeof(Server_Addr));//应用 地址和端口的设置
//进入监听状态
listen(Mysocket,20);
//接收客户端请求
struct sockaddr_in Client_Addr;
socklen_t client_addr_size = sizeof(Client_Addr);
std::cout << "ready accept"<<endl;
int client_sock = accept(Mysocket,(struct sockaddr *)&Client_Addr,&client_addr_size);//在此阻塞,等待客户端请求
std::cout << "received message"<<endl;
//向客户端发送数据
char str[] = "hello,world!";
Mymsg.msg_iov->iov_base = str;
sendmsg(client_sock,&Mymsg,0); //write(client_sock,str,sizeof(str));用write也是可以的
close(client_sock);
close(Mysocket);
}
void My_client(void){
char buf[128] = {0};
struct iovec buffer={
.iov_base = buf,
.iov_len = sizeof(buf)
};
struct msghdr Mymsg={
.msg_name = nullptr,
.msg_namelen = 0,
.msg_iov = &buffer,
.msg_iovlen = 1,
.msg_control = nullptr,
.msg_controllen = 0,
.msg_flags = 0
};
int Mysocket = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in Server_Addr;
memset(&Server_Addr,0,sizeof(Server_Addr));
Server_Addr.sin_family=AF_INET;
Server_Addr.sin_addr.s_addr=inet_addr("127.0.0.1");
Server_Addr.sin_port=htons(12345);
connect(Mysocket,(struct sockaddr*)&Server_Addr,sizeof(Server_Addr));
std::cout << "ready accept message"<<endl;
recvmsg(Mysocket,&Mymsg,0); //接收信息
printf("%s\n",Mymsg.msg_iov->iov_base);
std::cout << "read message done"<<endl;
close(Mysocket);
}
int main(int argc, char **argv){
string select;
_Start:
std::cout << "Select Mode Server or Client:(c/s)";
cin >> select;
std::cout << "select:" << select <<endl;
if(!select.compare("s")){
My_server();
}else if(!select.compare("c")){
My_client();
}else{
std::cout << "please input \"server\" or \"client\""<<endl;
std::cout << "Please re - enter parameters at boot time"<<endl;
goto _Start;
}
return 0;
}
/*
创建套接字: socket()
绑定本机端口: bind()
建立连接: connect(),accept()
侦听端口: listen()
数据传输: send(), recv()
输入/输出多路复用: select()
关闭套接字: closesocket()
*/
Makefile太麻烦了,利用cmake编译
/***CMakeLists.txt***/
cmake_minimum_required(VERSION 3.10.2)
PROJECT(mysocket)
add_executable(Mysocket Mysocket.cpp)