【发布时间】:2021-09-16 09:12:23
【问题描述】:
如果这个问题看起来微不足道,我深表歉意,但我是 C 和套接字编程的初学者。另外,英语不是我的母语。
我最近失败了一个要求我在 C 中实现一个接受 ipv4 和 ipv6 的 TCP 服务器客户端的 uni 作业,其中客户端将发送消息以添加、删除、列出和查询链表中的整数对存储在服务器中。所以,我用下面的代码制作了服务器和客户端(不包括链表和函数 strsplit 的代码):
服务器(servidor.c)
void func(int sockfd)
{
struct Node* posts = NULL;
char client_message[MAX];
char server_message[MAX];
char* str;
char **p;
int X, Y, i, j, k;
int n = 0;
char sX[5];
char sY[5];
for (;;)
{
recv(sockfd, client_message, sizeof(client_message), 0);
printf("> %s", client_message);
strcpy(str, client_message);
p = strsplit(str, " ");
memset(server_message, 0, MAX);
if(strncmp("kill", p[0], 4) == 0)
{
break;
}
else if(strncmp("add", p[0], 3) == 0)
{
if(n == 50)
{
strncat(server_message, "limit exceeded", 14);
}
else
{
strncat(server_message, p[1], 4);
strncat(server_message, " ", 1);
X = atoi(p[1]);
strtok(p[2], "\n");
strncat(server_message, p[2], 4);
strncat(server_message, " ", 1);
Y = atoi(p[2]);
i = push(&posts, X, Y);
if(i == 0)
{
strncat(server_message, "added", 5);
n++;
}
else
{
strncat(server_message, "already exists", 14);
}
free(p);
}
}
else if(strncmp("rm", p[0], 2) == 0)
{
strncat(server_message, p[1], 4);
strncat(server_message, " ", 1);
X = atoi(p[1]);
strtok(p[2], "\n");
strncat(server_message, p[2], 4);
strncat(server_message, " ", 1);
Y = atoi(p[2]);
i = deleteNode(&posts, X, Y);
if(i == 0)
{
strncat(server_message, "removed", 7);
n--;
}
else
{
strncat(server_message, "does not exist", 14);
}
free(p);
}
else if(strncmp("list", p[0], 4) == 0)
{
if(posts == NULL)
{
strncat(server_message, "none", 4);
}
else
{
struct Node *temp = posts;
while(temp != NULL)
{
bzero(sX, 4);
bzero(sY, 4);
snprintf(sX, sizeof(sX), "%d",temp->X);
snprintf(sY, sizeof(sY), "%d",temp->Y);
strncat(server_message, sX, 4);
strncat(server_message, " ", 1);
strncat(server_message, sY, 4);
strncat(server_message, " ", 1);
temp = temp->next;
}
free(temp);
}
}
else if(strncmp("query", p[0], 5) == 0)
{
if(posts == NULL)
{
strncat(server_message, "none", 4);
}
else
{
struct Node *temp1 = posts;
k = 19998;
X = atoi(p[1]);
strtok(p[2], "\n");
Y = atoi(p[2]);
while(temp1 != NULL)
{
i = abs(temp1->X - X);
j = abs(temp1->Y - Y);
i = i + j;
if(i <= k)
{
snprintf(sX, sizeof(sX), "%d",temp1->X);
snprintf(sY, sizeof(sY), "%d",temp1->Y);
k = i;
}
temp1 = temp1->next;
}
strncat(server_message, sX, 4);
strncat(server_message, " ", 1);
strncat(server_message, sY, 4);
strncat(server_message, " ", 1);
free(temp1);
}
}
strncat(server_message, "\n", 1);
printf("%s", server_message);
send(sockfd, server_message, sizeof(server_message), 0);
}
}
int main(int argc, char *argv[])
{
if(strncmp("v4", argv[1], 2) == 0)
{
int sockfd, connfd, len;
struct sockaddr_in servaddr, cli;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
exit(0);
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(atoi(argv[2]));
if ((bind(sockfd, (SA*)&servaddr, sizeof(servaddr))) != 0)
{
exit(0);
}
if ((listen(sockfd, 5)) != 0)
{
exit(0);
}
len = sizeof(cli);
connfd = accept(sockfd, (SA*)&cli, &len);
if (connfd < 0)
{
exit(0);
}
func(connfd);
close(sockfd);
}
else if(strncmp("v6", argv[1], 2) == 0)
{
int sockfd, connfd, len;
struct sockaddr_in6 servaddr, cli;
sockfd = socket(AF_INET6, SOCK_STREAM, 0);
if(sockfd == -1)
{
exit(0);
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin6_family = AF_INET6;
servaddr.sin6_addr = in6addr_any;
servaddr.sin6_port = htons(atoi(argv[2]));
if ((bind(sockfd, (SA*)&servaddr, sizeof(servaddr))) != 0)
{
exit(0);
}
if ((listen(sockfd, 5)) != 0)
{
exit(0);
}
len = sizeof(cli);
connfd = accept(sockfd, (SA*)&cli, &len);
if (connfd < 0)
{
exit(0);
}
func(connfd);
close(sockfd);
}
return 0;
}
客户端(cliente.c)
void func(int sockfd)
{
char buff[MAX];
int n;
for (;;)
{
bzero(buff, sizeof(buff));
n = 0;
while ((buff[n++] = getchar()) != '\n')
;
send(sockfd, buff, sizeof(buff), 0);
if ((strncmp(buff, "kill", 4)) == 0)
{
break;
}
bzero(buff, sizeof(buff));
recv(sockfd, buff, sizeof(buff), 0);
printf("> %s", buff);
}
}
int main(int argc, char *argv[])
{
if(strchr(argv[1], ':') != NULL)
{
int sockfd, connfd;
struct sockaddr_in6 servaddr, cli;
struct in6_addr result;
sockfd = socket(AF_INET6, SOCK_STREAM, 0);
if (sockfd == -1)
{
exit(0);
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin6_family = AF_INET6;
inet_pton(AF_INET6, argv[1], &result);
servaddr.sin6_addr = result;
servaddr.sin6_port = htons(atoi(argv[2]));
if (connect(sockfd, (SA*)&servaddr, sizeof(servaddr)) != 0)
{
exit(0);
}
func(sockfd);
close(sockfd);
}
else
{
int sockfd, connfd;
struct sockaddr_in servaddr, cli;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
exit(0);
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(argv[1]);
servaddr.sin_port = htons(atoi(argv[2]));
if (connect(sockfd, (SA*)&servaddr, sizeof(servaddr)) != 0)
{
exit(0);
}
func(sockfd);
close(sockfd);
}
return 0;
}
我自己测试,通过在单独的终端中打开服务器和客户端并一一发送消息,服务器似乎按预期工作。然而,我的教授却另有说法。作业失败后,他给我提供了用来给我的服务器评分的软件的python代码,如下:
run_tests.py
def run_basic_tests(exec_path, version, port, msg_type):
address = '127.0.0.1' if version == 'v4' else '::1'
list_dir = os.listdir('tests/in')
list_dir = sorted(list_dir)
for filename in list_dir:
filename = filename.split('.')[0]
_, result_file_path = tempfile.mkstemp()
server = sp.Popen(
[f'exec {exec_path} {version} {port}'], shell=True, stdout=sp.DEVNULL, stderr=sp.DEVNULL)
ret = os.system(
f'python3 client.py {address} {port} {msg_type} < tests/in/{filename}.in > {result_file_path}')
ret = os.WEXITSTATUS(ret)
if ret == 0:
if filecmp.cmp(f'tests/out/{filename}.out', result_file_path, shallow=False):
print(f'{filename}\t[OK]')
else:
print(f'{filename}\t[FAILED] (diff output)')
else:
print(f'{filename}\t[FAILED] (client exited with non-zero code {ret})')
os.remove(result_file_path)
server.kill()
if __name__ == '__main__':
if len(sys.argv) != 3:
print(f'usage: python3 {sys.argv[0]} <server> <port>')
sys.exit(0)
exec_path = sys.argv[1]
port = int(sys.argv[2])
if not exec_path.startswith('/'):
print('provide the full path to the executable')
sys.exit(0)
for address_family in ['v4', 'v6']:
for msg_type in ['single_msg_single_pkg', 'single_msg_multiple_pkg', 'multiple_msg_single_pkg']:
print('Testing IP' + address_family, msg_type)
run_basic_tests(exec_path, address_family, port, msg_type)
client.py
BUFFER = ''
def get_address_family(address):
try:
socket.inet_pton(socket.AF_INET, address)
return socket.AF_INET
except:
pass
try:
socket.inet_pton(socket.AF_INET6, address)
return socket.AF_INET6
except:
pass
sys.exit(1)
def create_socket(address, port):
address_family = get_address_family(address)
attempts = 0
client_socket = None
while attempts < 5:
try:
client_socket = socket.socket(address_family, socket.SOCK_STREAM)
client_socket.connect((address, port))
break
except socket.error:
client_socket = None
time.sleep(0.05)
attempts += 1
if not client_socket:
sys.exit(2)
return client_socket
# send one message
def send(client_socket, msg):
msg = msg.encode('ascii')
total_sent = 0
while total_sent != len(msg):
sent = client_socket.send(msg[total_sent:])
if sent == 0:
sys.exit(3)
total_sent += sent
# receive one complete message
def receive(client_socket):
global BUFFER
while True:
if '\n' in BUFFER:
msg = BUFFER[:BUFFER.index('\n')]
BUFFER = BUFFER[BUFFER.index('\n') + 1:]
return msg
try:
data = client_socket.recv(500 - len(BUFFER)).decode()
except socket.timeout:
sys.exit(7)
if not data:
sys.exit(4)
BUFFER += data
if len(BUFFER) >= 500:
sys.exit(5)
def run_multiple_msg_single_pkg(client_socket):
msg_buffer = []
for msg in sys.stdin:
msg_buffer.append(msg)
for i in range(0, len(msg_buffer), 2):
if i == len(msg_buffer) - 1:
send(client_socket, msg_buffer[i])
if msg_buffer[i] == 'kill\n':
client_socket.close()
sys.exit(0)
ret = receive(client_socket)
print(ret)
else:
msg = msg_buffer[i] + msg_buffer[i + 1]
send(client_socket, msg)
if msg_buffer[i] == 'kill\n':
client_socket.close()
sys.exit(0)
ret = receive(client_socket)
print(ret)
if msg_buffer[i + 1] == 'kill\n':
client_socket.close()
sys.exit(0)
ret = receive(client_socket)
print(ret)
def run_single_msg_multiple_pkg(client_socket):
for msg in sys.stdin:
send(client_socket, msg[:3])
time.sleep(0.1)
send(client_socket, msg[3:])
if msg == 'kill\n':
client_socket.close()
sys.exit(0)
ret = receive(client_socket)
print(ret)
def run_single_msg_single_pkg(client_socket):
for msg in sys.stdin:
send(client_socket, msg)
if msg == 'kill\n':
client_socket.close()
sys.exit(0)
ret = receive(client_socket)
print(ret)
if __name__ == '__main__':
if len(sys.argv) != 4:
print(
f'Usage: python3 {sys.argv[0]} <address> <port> [single_msg_single_pkg | single_msg_multiple_pkg | multiple_msg_single_pkg]')
sys.exit(6)
client_socket = create_socket(sys.argv[1], int(sys.argv[2]))
client_socket.settimeout(5)
if sys.argv[3] == 'single_msg_single_pkg':
run_single_msg_single_pkg(client_socket)
elif sys.argv[3] == 'single_msg_multiple_pkg':
run_single_msg_multiple_pkg(client_socket)
elif sys.argv[3] == 'multiple_msg_single_pkg':
run_multiple_msg_single_pkg(client_socket)
client_socket.close()
sys.exit(0)
用这个软件运行我的代码,没有一个测试工作。基本上,在接收和发送第一条消息后,我的服务器会停止与客户端的通信,每次测试都失败。这可能与我如何处理代码中的字符串有关,但老实说,我在这里不知所措。至少有人可以在正确的方向上推动我吗?
【问题讨论】:
-
重复。您假设
recv将返回完整的消息并且仅返回这个。这不是 TCP 中recv的工作方式——因为首先在 TCP 中没有消息这样的东西。测试客户端通过发送带有多次发送的“消息”(请参阅run_single_msg_multiple_pkg)和在一次发送中发送多条消息(请参阅run_multiple_msg_single_pkg),故意尝试您的代码是否正确处理了这部分。 -
@Afshin:我认为老师没有错(请参阅我对您答案的评论)。