对套接字的理解:
推荐一篇前辈的文章,讲的特别好:
Socket网络编程
创建地址
#include
struct sockaddr_in servaddr;//服务器端地址
sockaddr_in是IPv4套接口地址结构(网际套接口地址结构);
struct sockaddr_in {
uint8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
/*******************************/
struct in_addr{
union {
struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { u_short s_w1,s_w2; } S_un_w;
u_long S_addr;
} S_un;
#define s_addr S_un.S_addr
};
sockaddr_in详细解释
设置通信方式、IP、端口
memset(&servaddr, 0, sizeof(servaddr));//结构体清零
servaddr.sin_family = AF_INET;//设置使用ipv4进行通信
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//绑定到默认ip地址,将主机数转换成无符号长整型的网络字节顺序htonl:host to net long
servaddr.sin_port = htons(3100);//设置端口为3100
htonl()函数:把主机字节序转化为网络字节序(host to net long);
主机字节序和网络字节序的区别
htons()函数:把主机字节许转化为网络字节序(host to net short);
两种字节序之间相互转换的函数:
uint16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue);
uint16_t ntohs(uint16_t net16bitvalue);
uint32_t ntohl(uint32_t net32bitvalue);
listenfd = socket(AF_INET,SOCK_STREAM,0)
设定协议族为AF_INET(IPv4 协议),套接口类型为SOCK_STREAM(字节流套接口),协议设置为0,采用缺省配置;
socket函数原型:
#include
int socket(int family, int type, int protocal);
参数:
family参数指明协议族
family 说明
AF_INET IPv4 协议
AF_INET6 IPv6 协议
AF_LOCAL UNIX域协议
AF_ROUTE 路由套接口
AF_KEY 密钥套接口
type参数指明套接口类型
type 说明
SOCK_STREAM 字节流套接口
SOCK_DGRAM 数据报套接口
SOCK_SEQPACKET 有序分组套接口
SOCK_RAW 原始套接口
protocal参数指明某个协议类型常值,或者设置为0,以选择给定family和type组合的系统缺省值
protocal 说明
IPPROTO_TCP TCP传输协议
IPPROTO_UDP UDP传输协议
IPPROTO_SCTP SCTP传输协议
这里注意,不是所有的family与type的组合都是有效的
socket函数成功时返回一个非负整数值,它与文件描述符类似.称其为套接口描述符(socket descriptor),简称套接字(sockfd).
if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
return 0;
}
bind函数原型:
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
sockfd参数是套接口标识;
myaddr参数是服务器端的地址;
addrlen参数是地址长度;
if( listen(listenfd, 10) == -1){
printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
return 0;
}
listen函数原型:
int listen(int sockfd, int backlog);
listen函数仅由TCP服务器调用.当socket函数创建一个套接口时,它被假设为一个主动套接口,即是一个将调用connect函数发起连接的客户端接口.而listen函数把一个未连接的套接口转换成一个 被动套接口,指示内核接收指向该套接口的连接请求.这将导致套接口状态从closed转换到listen.
调用accept()函数,将返回值赋给套接字connfd
if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){
printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
continue;
}
accept()函数原型:
int accept(int sockfd, struct sockaddr* cliaddr, socklen_t *addrlen);
参数cliaddr和addrlen用来返回已连接的对端进程(客户)的协议地址.
addrlen是一个值-结果参数:调用前我们将由*addrlen所引用的整数值置为由cliaddr所指的套接口地址结构的长度,返回时,该整数值即为由内核存在该套接口地址结构内的确切字节数.
n = recv(connfd, buff, MAXLINE, 0);
函数原型:
int recv( SOCKET s, char *buf, int len, int flags);
s参数是socket套接字;
buf参数是指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
len参数指明缓冲区的长度;
flags参数一般置为0;
close(connfd);
connfd参数为socket套接字;
close(listenfd);
完整代码:server.cpp
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXLINE 16*1024
int main(int argc, char** argv){
int listenfd, connfd;
struct sockaddr_in servaddr;//服务器端地址
char buff[MAXLINE];
int n;
//创建套接字
if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
return 0;
}
memset(&servaddr, 0, sizeof(servaddr));//结构体清零
servaddr.sin_family = AF_INET;//设置使用ipv4进行通信
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//绑定到默认ip地址,将主机数转换成无符号长整型的网络字节顺序htonl:host to net long
servaddr.sin_port = htons(3100);//设置端口为3100
//将服务器端地址和sever套接字连接
if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
return 0;
}
//设置套接字为监听状态
if( listen(listenfd, 10) == -1){
printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
return 0;
}
printf("======waiting for client's request======\n");
while(1){
//接收的内容放到套接字connfd里
if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){
printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
continue;
}
n = recv(connfd, buff, MAXLINE, 0);
buff[n] = '\0';
printf("recv msg from client: %s\n*****%d", buff,n);
close(connfd);
}
close(listenfd);
return 0;
}
跟server端的创建相似
if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
return 0;
}
memset()函数的作用是将servaddr结构体里的变量都置为0;
端口号设置成与server一致,表明要连接的是我们刚才建立的那个server;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;//设置使用ipv4进行通信
servaddr.sin_port = htons(3100);//设置端口为3100
if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){
printf("inet_pton error for %s\n",argv[1]);
return 0;
}
将我们输入的目标IP参数(IPv4),转换为二进制存储到servaddr.sin_addr中.
此函数原型为:
int inet_pton(int family, const char* strptr, void *addrptr);
inet_pton()函数将转换由指针strptr所指的串,并存到addrptr中(二进制结果),地址表达格式可以为IPv4(AF_INET),也可以是IPv6(AF_INET6);avg[1]是从控制台读入的第二个参数;
if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
return 0;
}
connect函数原型:
int connect(ini sockfd, const struct sockaddr* servaddr, socklen_t addrlen);
这里sockfd是前面提到的由socket函数返回的套接口描述字,接着是一个指向套接口地址结构的指针和该结构的大小.套接口地址结构中必需包含有服务器的IP地址和端口号.端口号前面已经设定了为3100,IP地址在上一个步骤inet_pton函数中通过命令行参数argv[1]设定.
这个函数将client套接字和server的地址连接;
if( send(sockfd, sendline, strlen(sendline), 0) < 0){
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
函数原型:
int send( SOCKET s, const char FAR *buf, int len, int flags );
该函数的第一个参数指定发送端套接字描述符;
第二个参数指明一个存放应用程序要发送数据的缓冲区;
第三个参数指明实际要发送的数据的字节数;
第四个参数一般置0。
这里只描述同步Socket的send函数的执行流程。当调用该函数时,
(1)send先比较待发送数据的长度len和套接字s的发送缓冲的长度, 如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR;
(2)如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议是否正在发送s的发送缓冲中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么send就比较s的发送缓冲区的剩余空间和len
(3)如果len大于剩余空间大小,send就一直等待协议把s的发送缓冲中的数据发送完
(4)如果len小于剩余 空间大小,send就仅仅把buf中的数据copy到剩余空间里(注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议传的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里)。
如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。
要注意send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR。(每一个除send外的Socket函数在执 行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回 SOCKET_ERROR)
注意:在Unix系统下,如果send在等待协议传送数据时网络断开的话,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。详细解释
close(sockfd);
整体代码:client.cpp
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXLINE 4096
int main(int argc, char** argv){
int sockfd, n;
char recvline[4096], sendline[4096];
struct sockaddr_in servaddr;
//控制台传入要连接的端口ip
if( argc != 2){
printf("usage: ./client \n");
return 0;
}
//创建套接字
if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
return 0;
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;//设置使用ipv4进行通信
servaddr.sin_port = htons(3100);//设置端口为3100
if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){
printf("inet_pton error for %s\n",argv[1]);
return 0;
}
//将client套接字和端口连接
if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
return 0;
}
printf("send msg to server: \n");
fgets(sendline, 4096, stdin);
if( send(sockfd, sendline, strlen(sendline), 0) < 0){
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
close(sockfd);
return 0;
}
将server.cpp和client.cpp分别编译成可执行文件
用makefile编译cpp文件的详细步骤见另一篇博客:
https://blog.csdn.net/qq_41748900/article/details/82316662
运行server:
运行client:(127.0.0.1是本地的ip)
参考文献:
https://www.cnblogs.com/zkfopen/p/9441264.html
https://blog.csdn.net/qq_27923041/article/details/83857964
https://blog.csdn.net/maopig/article/details/17193021#commentBox
https://zhidao.baidu.com/question/529573816.html
https://blog.csdn.net/kvew/article/details/1336577
http://blog.chinaunix.net/uid-14261758-id-2825546.html
https://blog.csdn.net/qq_39540224/article/details/79180349
https://www.cnblogs.com/tianlangshu/p/6795681.html