我正在尝试学习 C 语言的套接字编程,我想创建一个管理聊天室的服务器,客户端可以连接该聊天室并向其他人发送消息
这是服务器代码: (您可以跳过它来查看代码重要部分的片段)
/* Server that manages a chat room */
// include necessary libraries
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/select.h>
#include <netdb.h>
// definitions
// function declarations
void printaddrs(struct addrinfo *res);
void printusage();
int isdig(char c);
int isaddr(char addr[INET6_ADDRSTRLEN]);
int isport(char port[6]);
char *strin(FILE *stream);
int getaddrfamily(char addr[INET6_ADDRSTRLEN]);
int main(int argc, char *argv[]) {
// argv[1] -> address for the server to bind to, if it is 0, print a list of addrs with getaddrinfo()
// argv[2] -> port for the server to bind to
// ~~~~~~~~~~~~ Argument validity checking - Start~~~~~~~~~~~~
if (argc < 3) {
printf("Not enough arguments\n\n");
printusage();
return 1;
}
if (argc > 3) {
printf("Too many arguments\n\n");
printusage();
return 1;
}
if (strlen(argv[1]) > INET6_ADDRSTRLEN) {
printf("IP address too long\n");
return 1;
}
if (isaddr(argv[1]) == 0) { // is not a valid IPv4 or IPv6 address
printf("Invalid <ip addr> argument\n\n");
printusage();
return 1;
}
if (strlen(argv[2]) > 6) {
printf("Port number too long\n");
return 1;
}
if (isport(argv[2]) == 0) { //
printf("Invalid <port> argument\n\n");
printusage();
return 1;
}
if (strcmp(argv[2], "0") == 0) {
printf("Argument <port> cannot be 0\n");
return 1;
}
if (strtol(argv[2], NULL, 10) < 1024) {
printf("CAUTION!\nBe cautious when using reserved port numbers\n");
}
// ~~~~~~~~~~~~ Argument validity checking - End ~~~~~~~~~~~~
struct addrinfo hints, *res;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; // Use both IPv4 and IPv6
hints.ai_socktype = SOCK_STREAM; // Use TCP/IP (not UDP)
char addrstr[INET6_ADDRSTRLEN] = "";
if (strcmp(argv[1], "0") == 0) { // User entered '0' as the first argument
int status = getaddrinfo(NULL, argv[2], &hints, &res);
if (status != 0) {
printf("getaddrinfo(): error: %s\n", gai_strerror(status));
return 1;
}
printaddrs(res);
printf("Select an address to bind to: ");
char *id = strin(stdin);
int count = 0;
for (; res != NULL; res = res->ai_next) {
if (strtol(id, NULL, 10) == count) {
if (res->ai_family == AF_INET) {
struct sockaddr_in *ipv4;
ipv4 = (struct sockaddr_in *)res->ai_addr;
inet_ntop(res->ai_family, &(ipv4->sin_addr), addrstr, sizeof(addrstr));
} else if (res->ai_family == AF_INET6) {
struct sockaddr_in6 *ipv6;
ipv6 = (struct sockaddr_in6 *)res->ai_addr;
inet_ntop(res->ai_family, &(ipv6->sin6_addr), addrstr, sizeof(addrstr));
} else {
printf("error: Choosing an address of an invalid address family is not allowed\n");
printf("Terminating...\n");
free(id);
freeaddrinfo(res);
return 1;
}
break;
}
count++;
}
free(id);
if (res == NULL) {
printf("error: Couldn't find entered id\n");
freeaddrinfo(res);
return 1;
}
} else { // User entered a specific IP address
strncpy(addrstr, argv[1], 4);
int addrfamily = getaddrfamily(addrstr);
if (addrfamily == AF_INET) {
struct sockaddr_in *ipv4;
inet_pton(AF_INET, addrstr, &(ipv4->sin_addr));
res->ai_addr = (struct sockaddr *)ipv4;
res->ai_family = AF_INET;
} else if (addrfamily == AF_INET6) {
struct sockaddr_in6 *ipv6;
printf("check1\n");
inet_pton(AF_INET6, addrstr, &(ipv6->sin6_addr));
printf("check2\n");
res->ai_addr = (struct sockaddr *)ipv6;
res->ai_family = AF_INET6;
} else {
printf("error: Invalid address\n");
freeaddrinfo(res);
return 1;
}
}
printf("Server address: %s\n", addrstr);
printf("Server port: %s\n", argv[2]);
printf("Success!\nTerminating...\n");
freeaddrinfo(res);
return 0;
}
// ~~~~~~~~~~~~ Function definitions ~~~~~~~~~~~~
// prints available addresses to possibly bind to
void printaddrs(struct addrinfo *res) {
int id = 0;
char addrstr[INET6_ADDRSTRLEN];
char ipver[30];
for (struct addrinfo *p = res; p != NULL; p = p->ai_next) {
if (p->ai_family == AF_INET) {
struct sockaddr_in *ipv4;
ipv4 = (struct sockaddr_in *)p->ai_addr;
inet_ntop(AF_INET, &(ipv4->sin_addr), addrstr, sizeof(addrstr));
strcpy(ipver, "ipv4");
} else if (p->ai_family == AF_INET6) {
struct sockaddr_in6 *ipv6;
ipv6 = (struct sockaddr_in6 *)p->ai_addr;
inet_ntop(AF_INET6, &(ipv6->sin6_addr), addrstr, sizeof(addrstr));
strcpy(ipver, "ipv6");
} else {
strcpy(addrstr, "error: Invalid address family");
strcpy(ipver, "error: Invalid address family");
}
printf("%d|%s\t%s\n", id, ipver, addrstr);
id++;
}
}
// prints the 'usage:' message
void printusage() {
printf("usage:\n./server <ip addr> <port>\n");
printf("<ip addr> - IPv4 or IPv6 address for the server to bind to (only dots or colons allowed)\n");
printf("<port> - Port number for the server to bind to\n");
}
// Returns 1 if character c is a digit (0-9)
int isdig(char c) {
switch(c) {
case '0':
return 1;
case '1':
return 1;
case '2':
return 1;
case '3':
return 1;
case '4':
return 1;
case '5':
return 1;
case '6':
return 1;
case '7':
return 1;
case '8':
return 1;
case '9':
return 1;
default:
return 0;
}
}
// Returns 1 if addr is a valid IPv4 or IPv6 address, otherwise returns 0
int isaddr(char addr[INET6_ADDRSTRLEN]) {
int isipv4 = 0, isipv6 = 0;
for (int i = 0; i < strlen(addr); i++) {
// If both dots and colons are present in addrm return 0
if (isipv4 && isipv6)
return 0;
if (addr[i] == '.')
isipv4 = 1;
else if (addr[i] == ':')
isipv6 = 1;
// if character is something other than a digit, dot or colon
if (isdig(addr[i]) == 0 && addr[i] != '.' && addr[i] != ':')
return 0;
}
return 1;
}
// Returns 1 if number(5 digits) is a valid port number, returns 0 if it's not
int isport(char port[6]) {
for (int i = 0; i < strlen(port); i++) {
if (isdig(port[i]) == 0)
return 0;
}
return 1;
}
// Takes string input from FILE *stream - !!! result must be free()'d !!!
char *strin(FILE *stream) {
char *str = NULL;
size_t size = 0;
int c;
int i = 0;
while (1) {
c = getc(stream);
if (stream != stdin) {
if(c == EOF)
break;
} else {
if(c == EOF || c =='\n')
break;
}
size++;
str = realloc(str, size * sizeof(char));
*(str + i) = c;
i++;
}
if (size == 0) {
return NULL;
}
str[size] = '\0';
return str;
}
// Returns the address family of a given valid IP address, or -1 if no dots or colons are present
int getaddrfamily(char addr[INET6_ADDRSTRLEN]) {
for (int i = 0; i < strlen(addr); i++) {
if (addr[i] == '.')
return AF_INET;
else if (addr[i] == ':')
return AF_INET6;
}
return -1;
}
但这才是重要的部分:
else { // User entered a specific IP address
strcpy(addrstr, argv[1]);
int addrfamily = getaddrfamily(addrstr);
if (addrfamily == AF_INET) {
struct sockaddr_in *ipv4;
inet_pton(AF_INET, addrstr, &(ipv4->sin_addr));
res->ai_addr = (struct sockaddr *)ipv4;
res->ai_family = AF_INET;
} else if (addrfamily == AF_INET6) {
struct sockaddr_in6 *ipv6;
printf("check1\n");
inet_pton(AF_INET6, addrstr, &(ipv6->sin6_addr));
printf("check2\n");
res->ai_addr = (struct sockaddr *)ipv6;
res->ai_family = AF_INET6;
} else {
printf("error: Invalid address\n");
freeaddrinfo(res);
return 1;
}
}
如果用户输入 0 作为第一个参数,则会打印出可能的地址列表,用户可以选择一个,但我希望可以指定一个地址作为参数,但是当我尝试复制它时 (argv [1]) 到 addrstr 字符串中,它会抛出分段错误...
我尝试使用 strncpy 代替,得到了相同的结果,但是我注意到,当我只指定复制 3 个字符(在 127.0.0.1 的情况下,仅复制“127”部分)时,它的工作方式就像魅力一样,但是当我只指定复制 3 个字符时,它会再次抛出分段错误我想复制4个字符... 难道这个点是这里的问题吗?
TL;博士:
当我尝试将参数
argv[1]
复制到之前初始化为 addrstr
的字符串 char addrstr[INET6_ADDRSTRLEN] = "";
时,它会抛出分段错误。
在
strncpy(addrstr, argv[1], 4);
行中,您最多复制从argv[1]
到
addrstr
的4个字符。
strncpy()
的文档说:
如果源长度大于 num,则不会在目标末尾隐式附加空字符。因此,在这种情况下,destination 不应被视为以 null 结尾的 C 字符串(这样读取会溢出)。您的
argv[1]
可能长于 4 个字符(IPv4 地址是由点分隔的四个数字组成的序列,每个数字表示为十进制数字序列;这是很多字符!)。那么,
/0
数组内将不会有
addrstr
终止符(只有参数的前四个字节,以及一些垃圾)。然后,将数组(“未终止”字符串)传递给
getaddrfamily()
,它调用
strlen()
,它查找
/0
,但没有找到它......并继续查找,超出分配的大小数组。迟早(如果越早越好),这会触发“分段错误”。