TCP通信的模式如下图,比较固定,对着图编代码就可以了:
服务器的main函数:
int main(int argc, char **argv)
{
int listenfd, connfd
pid_t childpid
socklen_t clilen
struct sockaddr_in cliaddr, servaddr//IPv4 address
/*socket*/
listenfd = socket(AF_INET, SOCK_STREAM, 0)//创建一个TCP的socket
if (-1 == listenfd) {
perror("socket erro.")
return -1
}
/*bind*/
//首先初始化server的IP地址和端口,然后再与刚刚创建的socket绑定
bzero(&servaddr, sizeof(servaddr))
servaddr.sin_family = AF_INET//设置协议簇
servaddr.sin_addr.s_addr = htonl(INADDR_ANY)//绑定本机的网卡
servaddr.sin_port = htons(1234)//绑定端口号,端口号可以随便取,大于1024就可以了
if (-1 == bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) {
perror("bind error.")
return -1
}
/*listen*/
//到这里已经有了一个绑定了IP地址和端口号的socket了,但是这个socket是个主动的socket,
//而作为server需要的是一个等待别的接入的被动的socket,所以得调用listen将这个socket设置为监听状态
//第二个参数表示服务器正在处理客户接入时的等待队列长度。
if (-1 == listen(listenfd, 10)) {
perror("listen error.")
return -1
}
while (1) {
clilen = sizeof(cliaddr)
//调用accept等待客户的接入,同时accept会用第二个参数返回客户的IP地址,
//通过第三个参数返回IP地址的实际大小,同时这个参数也是个值-结构参数,也就是
//在传递这个参数的时候,先给这个参数一个初始的值,然后函数中会根据具体的情况修改这个值,
//所以这里传递的是指针。
//当客户接入后,将返回一个成功和客服连接的socket描述符,通过读写这个socket即可实现和客户的通信了
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen)
if (-1 == connfd) {
if (EINTR == errno)
continue
perror("accept error.")
return -1
}
//通过fock创建子进程来处理客户请求,这里只是显示客户的IP地址、端口号和发送的文字,
//并将客户发送的文字回传给客户。
if (0 == (childpid=fork())) {//fock返回0 说明是子进程
//这里并没有关闭服务器的监听socket,只是将其引用计数减一,因为fork出来的子进程对父进程做了拷贝,
//所以这个监听socket的引用计数将由1变成2,而内核只有在一个socket的引用计数变为0才回去关闭它
close(listenfd)
//通过和客户连接的socket和客户通信
str_echo(connfd, cliaddr)
return 0
}
//父进程将和客户连接的socket的引用计数减一,同样并没有关闭这个socket
close(connfd)
}
return 0
}
服务器处理客户请求:
#define BSIZE 100
void str_echo(int sockfd, struct sockaddr_in cliaddr)
{
ssize_t n
char buf[BSIZE]
while ((n=read(sockfd, buf, BSIZE))) {//读取客户发送的信息
buf[n] = '\0'
printf("IP: %s, PORT: %d: %s\n", \
inet_ntoa(cliaddr), ntohs(cliaddr.sin_port), buf)//输出信息
n_write(sockfd, buf, n)//将受到的信息发送回客户,n_write的实现下面给出
}
}
客户端程序相对简单:
int main(int argc, char **argv)
{
int sockfd
struct sockaddr_in servaddr
if (2 != argc) {
printf("usage: ./client 127.0.0.1")//
return -1
}
/*socket*/
sockfd = socket(AF_INET, SOCK_STREAM, 0) //创建一个socket
if (-1 == sockfd) {
perror("socket error.")
return -1
}
/*connect*/
//首先初始化要连接的服务器的IP地址和端口号,然后调用connect去连接这个服务器
bzero(&servaddr, sizeof(servaddr))
servaddr.sin_family = AF_INET
servaddr.sin_port = htons(1234)
inet_pton(AF_INET, argv[1], &servaddr.sin_addr)
if (-1 == connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) {
perror("connect error.")
return -1
}
//连接成功后就可以通过这个socket来和服务器通信了
str_cli(stdin, sockfd)
return 0
}
n_write 和 readline
/*write n bytes to fd*/
ssize_t n_write (int fd, void *buf, size_t n)
{
size_t nleft = n
ssize_t nwriten
char *bufp = buf
while (nleft >0) {
if ((nwriten = write (fd, bufp, nleft)) <= 0) {
if (EINTR == errno)
nwriten = 0
else
return -1
}
nleft -= nwriten
bufp += nwriten
}
return n
}
/*read line from fd*/
ssize_t readline (int fd, void *buf, size_t maxlen)
{
ssize_t n, rc
char c, *bufp
bufp = buf
for (n = 1n <maxlenn ++) {
again:
if (1 == (rc = read (fd, &c, 1))) {
*bufp ++ = c
if ('\n' == c)
break /*newline is stored*/
} else if (rc == 0) {
*bufp = 0
return (n - 1) /*EOF, n-1 bytes were read*/
} else {
if (EINTR == errno) /*interrupt*/
goto again
return -1 /*Erro, set the errno by read ()*/
}
}
*bufp = 0
return n
}
运行结果:
因为客户端没有指定IP地址和端口,所以其IP和端口都是内核随机分配的。
100万并发连接服务器笔记之Java Netty处理1M连接的预测如下:1、不说Netty会如何,服务器都有可能直接崩溃掉。
2、按平均每链接传输数据1K,100W链接大概数据量会在1G左右,G级服务器网卡也受不了的。
3、在网络编程中对单机来讲,成功解决了C10K的问题,这种M级别的链接,可能暂时解决不了。
4、对于如此大的并发,一般都是通过负载均衡的方式进行处理,如新浪微博,同时在线100W以上,通过约100多个节点处理,每个节点也就才10000并发左右。
总结如下:
1、JVM需要提前指定堆大小,相比Erlang/C,这可能是个麻烦。
2、GC(垃圾回收),需要持续不断的根据日志、JVM堆栈信息、运行时情况进行JVM参数微调。
3、设置一个最大连接目标,多次测试达到顶峰,然后释放所有连接,反复观察内存占用,获得一个较为合适的系统运行内存值。
4、Eclipse Memory Analyzer结合jmap导出堆栈DUMP文件,分析内存泄漏,还是很方便的。
5、想修改运行时内容,或者称之为热加载,默认不可能。
欢迎分享,转载请注明来源:夏雨云
评论列表(0条)