- 本地的进程间通讯方式有管道、消息队列、共享内存、信号量,而网络中的进程间通信则是由socket方式搞定。还是本着一切皆文件的思想,该编程模式大概流程为 open------------>read/write-------------->close; 命名空间的元素都可称为套接字接口。一个套接字接口构成一个连接的一端,而一个连接可完全由一对套接字接口规定。
- 相关函数:
int socketfd = socket(int socket_family, int socket_type, int protocol)
第一个参数为socket的协议族domain,由一组宏定义表示,一般使用AF_INET,表示使用IPV4协议。
- 第二个参数为socket的类型,主要分为SOCK_STREAM和SOCK_DGRAM,分别对应TCP协议和UDP协议。
- 第三个参数为socket的特定协议,可以使用0表示使用默认的协议。
- 函数返回值为创建的socket文件描述符。-1表示创建失败。
- 头文件<sys/socket.h>
绑定IP地址和端口
int bind(int sockfd, strucconst t sockaddr *addr, socklen_t addrlen)
- 第二个参数是一个结构体sockaddr的指针。对于TCP来说,使用sockaddr_in
struct sockaddr {
u_charsa_len; //长度
u_short sa_family; //协议
char sa_data[14]; //数据
};
struct sockaddr_in
{
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[sizeof (struct sockaddr) -
(sizeof (unsigned short int)) -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
//里面的in_addr结构体:
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
- 第三个参数是第二个参数的结构体的长度。
请求链接 (客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
- 第一个参数为创建的socket文件描述符
- 第二个参数为sockaddr结构体指针,服务器端的地址信息
- 第三个参数为第二个参数的长度
- 可以使用inet_addr将IP地址从字符串形式转换为二进制形式
启动监听
int listen(int sockfd, int backlog)
- 第二个参数为同时监听的数量
接收客户端的连接请求
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
- 第二个参数为sockaddr结构体指针,用来接收客户端的地址信息
- 第三个参数为第二个参数的长度。
- 返回值为对应的客户端socket的文件描述符,失败则返回-1。
读写数据
- ssize_t read(int fd, void *buf, size_t count)
- ssize_t write(int fd, const void *buf, size_t count)
- 客户端与服务器具体的编程流程
TcpClient
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h> // IP地址转换函数
#include <netinet/in.h> // 字节序转换函数
int main()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
assert(-1 != sockfd);
struct sockaddr_in ser;
memset(&ser, 0, sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(6000);
inet_aton("127.0.0.1", (struct in_addr*)&ser.sin_addr);
int res = connect(sockfd, (struct sockaddr*)&ser, sizeof(ser));
assert(-1 != res);
while(1)
{
printf("please input: ");
char data[128] = {0};
fgets(data, 128, stdin);
if(strncmp(data, "bye", 3) == 0)
{
close(sockfd);
break;
}
send(sockfd, data, strlen(data) - 1, 0);
char buff[128] = {0};
int n = recv(sockfd, buff, 127, 0);
if(n <= 0)
{
printf("error\n");
close(sockfd);
break;
}
printf("n == %d: %s\n", n, buff);
}
}
TcpServer
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h> // IP地址转换函数
#include <netinet/in.h> // 字节序转换函数
int main()
{
// 协议族
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
assert(-1 != listenfd);
struct sockaddr_in ser, cli;
memset(&ser, 0, sizeof(ser));
ser.sin_family = AF_INET; // 地址族
ser.sin_port = htons(6000);
inet_aton("127.0.0.1", (struct in_addr*)&ser.sin_addr);
int res = bind(listenfd, (struct sockaddr*)&ser, sizeof(ser));
assert(-1 != res);
listen(listenfd, 2);
while(1)
{
int len = sizeof(cli);
// c特定客户端和服务器连接的文件描述符
// accept函数只有在有客户端连接的情况下返回
int c = accept(listenfd, (struct sockaddr*)&cli, &len); // 阻塞运行
assert(-1 != c);
while(1)
{
char buff[128] = {0};
int n = recv(c, buff, 2, 0); // 阻塞 有数据可读或者客户端退出都会返回
if(n == 0)
{
printf("client unlink\n");
close(c);
break;
}
else if(n == -1)
{
printf("error\n");
close(c);
break;
}
printf("n == %d: %s\n", n, buff);
send(c, "OK", 2, 0);
}
}
close(listenfd);
}