1: 先看下客户端和服务端基本的交互流程
两个基础结构体:
这两个结构体其实是等价的,用于保存socket绑定信息,包括协议族,ip地址,端口。都占用了32个字节(算内存对齐),但 sockaddr 将端口和ip地址写在了一起,保存在 sa_data 中,不便于操作。但操作系统因为遗留问题,所有网络编程接口接收参数都是 sockaddr,因此在定义的时候,会使用 sockaddr_in ,使用的时候强转为 sockaddr。
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 sockaddr {
__uint8_t sa_len; /* total length */
sa_family_t sa_family; /* [XSI] address family */
char sa_data[14]; /* [XSI] addr value (actually larger) */
};
主要涉及的系统调用有:
套接字生成
//1:协议族,一般选 AF_INET 代表ipv4
//2:连接类型,SOCK_STREAM 代表稳定连接,即tcpip,SOCK_DGRAM 代表不稳定连接 UDP
//3:默认为0
//返回值为socket的标识号
int socket(int, int, int);
绑定
//1:要绑定套接字的标识号
//2:绑定信息(这里一般将sockaddr_in强转为sockaddr)
//3:绑定结构体长度
//返回值-1代表失败
int bind(int, const struct sockaddr *, socklen_t)
监听
//1:监听的socket标识
//2:可以排队的最大连接数
//返回值-1代表失败
int listen(int, int)
建立连接
//1:套接字标识
//后面几个参数就不说了
//返回连接标识
int connect(int, const struct sockaddr *, socklen_t)
接收客户端连接
//1:套接字标识
//2:发送方协议地址
//3:长度
//返回一个新的描述字,用于表示这次连接
int accept(int, struct sockaddr * __restrict, socklen_t * __restrict)
读取接收信息的api有两组
read / write 和 recv / send,区别在于后者支持的模式更多些。
服务端代码:
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <iostream>
//using namespace std;
#define MAXLINE 4096
int main(int argc, const char * argv[]) {
int listenfd, connfd;
struct sockaddr_in servaddr;
char buff[4096];
size_t n;
//1: 建立套接字
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
printf("create socket error: %s %d\n",strerror(errno), errno);
return 0;
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
// inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); //两种都可以
servaddr.sin_port = htons(6666); //字节序转化,网络字节序是大端字节序
//2: 绑定端口
//sockaddr_in中区分了端口和服务端地址,由于两个结构体大小相同,因此可以相互转化
if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
printf("bind error: %s %d\n",strerror(errno), errno);
return 0;
}
//3: 监听客户端请求,在没有收到请求之前,代码阻塞在这儿
if (listen(listenfd, 10) == -1) {
printf("listen error: %s %d\n",strerror(errno), errno);
return 0;
}
while (1) {
//4: 接收客户端请求,返回值是一个描述字,用于代表和客户端的连接
if ((connfd = accept(listenfd, nullptr, nullptr)) == -1) {
printf("accept error: %s %d\ n",strerror(errno), errno);
}
n = recv(connfd, buff, MAXLINE, 0);
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
close(connfd);
}
close(listenfd);
return 0;
}
客户端代码:
#import <Foundation/Foundation.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
int main() {
int sockfd;
char sendline[4096];
struct sockaddr_in servaddr;
//1: 生成套接字
if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) { //SOCK_STREAM 稳定连接->tcp,AF_INET->ipv4 地址
printf("create socket error: %s %d",strerror(errno),errno);
}
//2: 初始化连接信息
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET; //AF_INET ipv4 地址
servaddr.sin_port = htons(6666);
if (inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr) <= 0) { //点分十进制转二进制整数,127.0.0.1 -> 0001000000001110
printf("inet_pton error: %s %d",strerror(errno),errno);
return 0;
}
//3: 建立连接
if (connect(sockfd, (sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
printf("connet error: %s %d",strerror(errno),errno);
return 0;
}
//4: 发送数据
printf("send msg to server:\n");
while (1) {
fgets(sendline, 4096, stdin);
if (send(sockfd, sendline, strlen(sendline), 0) < 0) {
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
}
}
close(sockfd);
return 0;
}