我仍然认为自己是一个 C 初学者,我正在尝试学习套接字编程,老实说我有点挣扎。
我正在尝试创建一个服务器来管理一个字符室,任意数量的客户端都可以连接到该字符室并与所有其他客户端聊天。
这里有很多代码:
服务器.c
/* Server that manages a chatroom */
// Necessary includes
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <errno.h>
#include <pthread.h>
// Custom header files includes
#include "func/func.h"
int main(int argc, char *argv[]) {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~ Argument validation - START ~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if (argc == 1) {
printusage();
return 1;
}
if (argc > 3) {
printf("Too many arguments\n");
printusage();
return 1;
}
if (argc < 3) {
printf("Not enough arguments\n");
printusage();
return 1;
}
int status = is_addr(argv[1]);
if (status != 1) {
switch (status) {
case ELONG:
printf("<addr> argument too long\n");
break;
case ECHAR:
printf("<addr> argument contains invalid characters\n");
break;
case EINVALID:
printf("<addr> argument is not a valid IPv4 or IPv6 address\n");
break;
default:
printf("unknown error...\n");
break;
}
return 1;
}
status = is_port(argv[2]);
if (status != 1) {
switch(status) {
case ELONG:
printf("<port> argument too long\n");
break;
case ECHAR:
printf("<port> argument not a valid port number\n");
break;
default:
printf("unknown error...\n");
}
return 1;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~ Argument validation - END ~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
struct addrinfo hints, *res;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
status = getaddrinfo(NULL, argv[2], &hints, &res);
if (status != 0) {
printf("getaddrinfo(): error: %s\n", gai_strerror(status));
return 1;
}
printf("getaddrinfo(): Success!\n");
char addrstr[INET6_ADDRSTRLEN];
int iptest = 0;
struct sockaddr_in *ipv4;
struct sockaddr_in6 *ipv6;
if (strcmp(argv[1], "0") == 0) { // List of options
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) {
break;
}
count++;
}
if (res->ai_family == AF_INET) {
ipv4 = (struct sockaddr_in *)res->ai_addr;
inet_ntop(AF_INET, &ipv4->sin_addr, addrstr, sizeof(addrstr));
} else if (res->ai_family == AF_INET6) {
ipv6 = (struct sockaddr_in6 *)res->ai_addr;
inet_ntop(AF_INET6, &ipv6->sin6_addr, addrstr, sizeof(addrstr));
} else {
printf("Invalid address family!\n");
freeaddrinfo(res);
return 1;
}
free(id);
} else { // User entered a specific address
strcpy(addrstr, argv[1]);
int addrfamily = getaddrfamily(addrstr);
if (addrfamily == AF_INET) {
ipv4 = malloc(sizeof(struct sockaddr_in));
printf("addrstr: %s\n", addrstr);
inet_pton(AF_INET, addrstr, &ipv4->sin_addr);
res->ai_addr = (struct sockaddr *)ipv4;
res->ai_addr->sa_family = AF_INET;
res->ai_family = AF_INET;
res->ai_addrlen = sizeof(struct sockaddr_in);
iptest = 4;
} else if (addrfamily == AF_INET6) {
ipv6 = malloc(sizeof(struct sockaddr_in6));;
inet_pton(AF_INET6, addrstr, &ipv6->sin6_addr);
res->ai_addr = (struct sockaddr *)ipv6;
res->ai_addr->sa_family = AF_INET6;
res->ai_family = AF_INET6;
res->ai_addrlen = sizeof(struct sockaddr_in6);
iptest = 6;
} else {
printf("Invalid address family!\n");
freeaddrinfo(res);
return 1;
}
}
printf("~~~~~~~~~~~~~~~~~~~~\n");
printf("Server address: %s\n", addrstr);
printf("Server port: %s\n", argv[2]);
int serverfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (serverfd == -1) {
printf("socket(): error: %s\n", strerror(errno));
terminate(res, ipv4, ipv6, iptest);
return 1;
}
printf("socket(): Success!\n");
if (bind(serverfd, res->ai_addr, res->ai_addrlen) == -1) {
printf("bind(): error: %s\n", strerror(errno));
terminate(res, ipv4, ipv6, iptest);
return 1;
}
printf("bind(): Success!\n");
if (listen(serverfd, 16) == -1) {
printf("listen(): error: %s\n", strerror(errno));
terminate(res, ipv4, ipv6, iptest);
return 1;
}
printf("Server listening on port %s...\n", argv[2]);
int *clientfds; // array of connected clients (file descriptors)
pthread_t accept_t; // thread for accepting new connections
pthread_t *read_t; // array of threads for reading from existing connections
pthread_t *write_t; // array of threads for writing to existing connections
serverinfo server = {serverfd, NULL, 0};
pthread_create(&accept_t, NULL, handle_new, (void *)&server);
pthread_join(accept_t, NULL);
terminate(res, ipv4, ipv6, iptest);
return 0;
}
客户端.c
/* Client to connect to server.c */
// Necessary includes
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <unistd.h>
#include <netdb.h>
#include <errno.h>
#include <pthread.h>
#include "func/func.h"
int main(int argc, char *argv[]) {
struct addrinfo hints, *res;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
int status = getaddrinfo(argv[1], argv[2],&hints, &res);
if (status != 0) {
printf("getaddrinfo(): error: %s\n", gai_strerror(status));
return 1;
}
printf("getaddrinfo(): Success!\n");
int clientfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (clientfd == -1) {
printf("socket(): error: %s\n", strerror(errno));
freeaddrinfo(res);
return 1;
}
printf("socket(): Success!\n");
if (connect(clientfd, res->ai_addr, res->ai_addrlen) == -1) {
printf("connect(): error: %s\n", strerror(errno));
close(clientfd);
freeaddrinfo(res);
return 1;
}
printf("Connected to server!\n");
}
func.h(包含函数声明)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <pthread.h>
#include <errno.h>
#define ELONG -1 // addr is too long
#define ECHAR -2 // addr contains invalid characters
#define EINVALID -3 // addr is invalid for other reasons
typedef struct serverinfo {
int si_serverfd; // file descriptor of the server
int *si_clientfds; // array of connected clients (file descriptors)
int si_size; // size of si_clientfds (number of connected clients))
} serverinfo;
// func/strin.c
char *strin(FILE *stream);
// func/validation.c
int is_digit(char c);
int is_ipv4(char *addr);
int is_ipv6(char *addr);
int is_addr(char *addr);
int is_port(char *port);
int getaddrfamily(char *addr);
void printusage();
// func/printaddrs.c
void printaddrs(struct addrinfo *res);
// func/terminate.c
void terminate(struct addrinfo *res, struct sockaddr_in *ipv4, struct sockaddr_in6 *ipv6, int iptest);
// func/pthread.c
void *handle_new(void *si);
func/pthread.c
/* <pthread.h> related functions */
#include "func.h"
//
void *handle_new(void *si) {
serverinfo *server = (serverinfo *)si;
printf("Waiting for connection...\n");
int tempfd = accept(server->si_serverfd, NULL, NULL);
if (tempfd == -1)
printf("accept_t: accept(): error\n");
printf("New connection!\n");
server->si_size++;
server->si_clientfds = realloc(server->si_clientfds, server->si_size * sizeof(int));
return NULL;
}
func/printaddrs.c
#include "func.h"
void printaddrs(struct addrinfo *res) {
struct sockaddr_in *ipv4;
struct sockaddr_in6 *ipv6;
char addrstr[INET6_ADDRSTRLEN];
int count = 0;
char ipver[5];
for (struct addrinfo *p = res; p != NULL; p = p->ai_next) {
if (p->ai_family == AF_INET) {
ipv4 = (struct sockaddr_in *)p->ai_addr;
inet_ntop(AF_INET, &ipv4->sin_addr, addrstr, sizeof(addrstr));
strcpy(ipver, "IPv4");
printf("%d|%s\t%s\n", count, ipver, addrstr);
}else if (p->ai_family == AF_INET6) {
ipv6 = (struct sockaddr_in6 *)p->ai_addr;
inet_ntop(AF_INET6, &ipv6->sin6_addr, addrstr, sizeof(addrstr));
strcpy(ipver, "IPv6");
printf("%d|%s\t%s\n", count, ipver, addrstr);
}
count++;
}
}
func/validation.c
#include "func.h"
// Returns 1 if character c is a digit, otherwise returns 0
int is_digit(char c) {
switch (c) {
case '0' ... '9':
return 1;
case 'a' ... 'f':
return 1;
case 'A' ... 'F':
return 1;
default:
return 0;
}
}
// Returns 1 if addr is a valid IPv4 address, otherwise returns 0
int is_ipv4(char *addr) {
int dots = 0, octets = 0;
char number[INET6_ADDRSTRLEN] = "0";
unsigned long len = strlen(addr) + 1; // Length of the string INCLUDING the null terminator
for (int i = 0; i < len; i++) {
if (addr[i] == '.' || addr[i] == '\0') {
if (is_digit(addr[i - 1]))
octets++;
dots++;
if (strtol(number, NULL, 10) > 255) {
return 0;
}
strcpy(number, "0");
}
strcat(number, &addr[i]);
}
if (dots - 1 != 3 || octets != 4) {
return 0;
}
return 1;
}
int is_ipv6(char *addr) {
int colons = 0, hextets = 0;
char number[INET6_ADDRSTRLEN] = "0";
unsigned long len = strlen(addr) + 1; // Length of the string INCLUDING the null terminator
for (int i = 0; i < len; i++) {
if (addr[i] == ':' || addr[i] == '\0') {
if (is_digit(addr[i - 1]))
hextets++;
colons++;
if (strtol(number, NULL, 16) > 65535) { // FFFF (16)
return 0;
}
strcpy(number, "0");
}
strcat(number, &addr[i]);
}
if (colons - 1 < 2 || colons - 1 > 7) {
return 0;
}
if (colons != 7) {
if (addr[0] != ':' && addr[strlen(addr) - 1] != ':')
return 0;
}
return 1;
}
/** Returns 1 if addr is a valid IPv4 or IPv6 address, or one of the following error codes
* ELONG (-1) -> addr is too long
* ECHAR (-2) -> addr contains an invalid character
* EINVALID (-3) -> addr is not a valid IPv4 or IPv6 address
*/
int is_addr(char *addr) {
int dots = 0, colons = 0;
unsigned long len = strlen(addr);
if (len * sizeof(char) >= INET6_ADDRSTRLEN)
return ELONG;
for (int i = 0; i < len; i++) {
// If character is not a digit, colon or dot OR if there are both dots and colons present
if ((is_digit(addr[i]) == 0 && addr[i] != '.' && addr[i] != ':') || (dots > 0 && colons > 0))
return ECHAR;
// Increment dots or colons if character is a dot or a colon
if (addr[i] == '.')
dots++;
else if (addr[i] == ':')
colons++;
}
if (dots > 0) {
if (is_ipv4(addr)) return 1;
else return EINVALID;
} else if (colons > 0) {
if (is_ipv6(addr)) return 1;
else return EINVALID;
}
}
/** Returns 1 if port is a valid port number, or one of the following error codes
* ELONG (-1) -> port is too long
* ECHAR (-2) -> port contains an invalid character
*/
int is_port(char *port) {
unsigned long len = strlen(port);
if (len > 6)
return ELONG;
for (int i = 0; i < len; i++) {
if (is_digit(port[i]) == 0)
return ECHAR;
}
return 1;
}
// Returns the address family of a given !VALID! IP address (AF_INET or AF_INET6),
// or -1 in case there is no dot or colon present
int getaddrfamily(char *addr) {
unsigned long len = strlen(addr);
for (int i = 0; i < len; i++) {
if (addr[i] == '.')
return AF_INET;
else if (addr[i] == ':')
return AF_INET6;
}
return -1;
}
// prints the 'usage' message
void printusage() {
printf("usage:\n./server <addr> <port>\n\n");
printf("<addr> - IPv4 or IPv6 address for the server to bind to (Enter 0 for list of options)\n");
printf("<port> - port number for the server to bind to\n");
}
代码结束 其他函数不应该很重要,只需知道在我的情况下 strin() 从 stdin 获取输入,而 Terminate() free() 是需要 free()'d 的所有内容
问题来了
当我启动 server.c...
./server 127.0.0.1 8080
getaddrinfo(): Success!
addrstr: 127.0.0.1
~~~~~~~~~~~~~~~~~~~~
Server address: 127.0.0.1
Server port: 8080
socket(): Success!
bind(): Success!
Server listening on port 8080...
Waiting for connection...
看起来效果很好...
但是当我以同样的方式启动client.c时...
./client 127.0.0.1 8080
getaddrinfo(): Success!
socket(): Success!
connect(): error: Connection refused
它只是说连接被拒绝
我尝试从头开始制作简单的回显服务器和客户端(客户端发送消息,接收相同的消息)文件来测试我的计算机端是否有问题(防火墙等),它工作得很好......
我知道我问了很多问题,而且这里有数百行代码,但如果有人有时间并且更重要的是有心情在这里帮助我,我将非常感激!
正如@yvs2014所说,我需要指定
ipv4->sin_port = htons(strtol(argv[2], NULL, 10));
(我在这里使用strtol()而不是atoi())
非常感谢大家对我的帮助!我非常感激! 你让我在 C 方面继续进步!