通过该简单的demo学习socket网络编程,注释会详细解释每一行的作用,并且代码可以编译运行。
server.c
#include <sys/types.h> /* basic system data types */
#include <sys/socket.h> /* basic socket definitions */
#include <sys/time.h> /* timeval{} for select() */
#include <time.h> /* timespec{} for pselect() */
#include <netinet/in.h> /* sockaddr_in{} and other Internet defns */
#include <arpa/inet.h> /* inet(3) functions */
#include <errno.h>
#include <fcntl.h> /* for nonblocking */
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h> /* for S_xxx file mode constants */
#include <sys/uio.h> /* for iovec{} and readv/writev */
#include <unistd.h>
#include <sys/wait.h>
#include <sys/un.h> /* for Unix domain sockets */
#include <sys/select.h> /* for convenience */
#include <sys/sysctl.h>
#include <poll.h> /* for convenience */
#include <strings.h> /* for convenience */
#include <sys/ioctl.h>
#include <pthread.h>
size_t readn(int fd, void *buffer, size_t size) {
char *buffer_pointer = buffer;
int length = size;
while (length > 0) {
int result = read(fd, buffer_pointer, length);
if (result < 0) {
if (errno == EINTR)
continue; /* 考虑非阻塞的情况,这里需要再次调用read */
else
return (-1);
} else if (result == 0)
break; /* EOF(End of File)表示套接字关闭 */
length -= result;
buffer_pointer += result;
}
return (size - length); /* 返回的是实际读取的字节数*/
}
void read_data(int sockfd) {
ssize_t n;
char buf[1024];
int time = 0;
for (;;) {
fprintf(stdout, "block in read\n");
if ((n = readn(sockfd, buf, 1024)) == 0)
return;
time++;
fprintf(stdout, "1K read for %d \n", time);
// 接收设置延迟时间,观察client发送数据和server接收数据,client将数据写入到缓冲区之后就会立即返回成功
sleep(5);
}
}
int main(int argc, char **argv) {
int listenfd, connfd;
socklen_t clilen;
// 声明地址信息结构体
struct sockaddr_in cliaddr, servaddr;
// 创建套接字
listenfd = socket(AF_INET, SOCK_STREAM, 0);
// 将指定内存区域的前 n 个字节全部设置为零,用于初始化内存区域
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
// INADDR_ANY IPv4通配地址 IN6ADDR_ANY IPv6通配地址
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
// 设置端口号,设置为0时由操作系统选择一个空闲的端口
// htons函数:将一个无符号短整型(通常是16位)的主机数值转换为网络字节顺序
servaddr.sin_port = htons(12345);
// bind到本地地址,端口为12345
// bind函数套接字和地址关联
bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
// 初始化创建的套接字,可以认为是一个"主动"套接字,其目的是之后主动发起请求
// 通过 listen 函数,可以将原来的"主动"套接字转换为"被动"套接字
listen(listenfd, 1024);
/* 循环处理用户请求 */
for (;;) {
clilen = sizeof(cliaddr);
// 客户端发起连接请求,accept函数接受请求建立连接
// 第一个参数listenfd是用于监听的套接字,返回的是新建立连接的套接字
connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen);
read_data(connfd); /* 读取数据 */
close(connfd); /* 关闭连接套接字,注意不是监听套接字*/
}
}
client.c
#include <sys/types.h> /* basic system data types */
#include <sys/socket.h> /* basic socket definitions */
#include <sys/time.h> /* timeval{} for select() */
#include <time.h> /* timespec{} for pselect() */
#include <netinet/in.h> /* sockaddr_in{} and other Internet defns */
#include <arpa/inet.h> /* inet(3) functions */
#include <errno.h>
#include <fcntl.h> /* for nonblocking */
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h> /* for S_xxx file mode constants */
#include <sys/uio.h> /* for iovec{} and readv/writev */
#include <unistd.h>
#include <sys/wait.h>
#include <sys/un.h> /* for Unix domain sockets */
#include <sys/select.h> /* for convenience */
#include <sys/sysctl.h>
#include <poll.h> /* for convenience */
#include <strings.h> /* for convenience */
#include <sys/ioctl.h>
#include <pthread.h>
#include <stdarg.h> /* ANSI C header file */
#include <syslog.h> /* for syslog() */
# define MESSAGE_SIZE 102400
void error(int status, int err, char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
if (err)
fprintf(stderr, ": %s (%d)\n", strerror(err), err);
if (status)
exit(status);
}
void send_data(int sockfd) {
char *query;
query = malloc(MESSAGE_SIZE + 1);
for (int i = 0; i < MESSAGE_SIZE; i++) {
query[i] = 'a';
}
query[MESSAGE_SIZE] = '\0';
const char *cp;
cp = query;
size_t remaining = strlen(query);
while (remaining) {
int n_written = send(sockfd, cp, remaining, 0);
fprintf(stdout, "send into buffer %d \n", n_written);
if (n_written <= 0) {
error(1, errno, "send failed");
return;
}
remaining -= n_written;
cp += n_written;
}
return;
}
int main(int argc, char **argv) {
int sockfd;
struct sockaddr_in servaddr;
if (argc != 2)
error(1, 0, "usage: tcpclient <IPaddress>");
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(12345);
inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
int connect_rt = connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
if (connect_rt < 0) {
error(1, errno, "connect failed ");
}
send_data(sockfd);
exit(0);
}
对于 send 来说,返回成功仅仅表示数据写到发送缓冲区成功,并不表示对端已经成功收到。
对于 read 来说,需要循环读取数据,并且需要考虑 EOF 等异常条件。